Compare commits
17 Commits
08029ec604
...
v0.0.2
| Author | SHA1 | Date | |
|---|---|---|---|
| 57fd51be3e | |||
| ce70251057 | |||
| b47c789612 | |||
| dd853b8e1b | |||
| a0585b0814 | |||
| 2100b82dad | |||
| 15a4300db5 | |||
| fed39c01e8 | |||
| 0a4f1419eb | |||
| 793c83e18c | |||
| 20bc0ffcb4 | |||
| 8e09fd106e | |||
| 73ca0ff096 | |||
| 425f1c8627 | |||
| 730332cfb0 | |||
| 1d70a83759 | |||
| 0299012725 |
41
.github/workflows/ci.yml
vendored
41
.github/workflows/ci.yml
vendored
@@ -45,7 +45,6 @@ jobs:
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
targets: ${{ matrix.target }}
|
||||
components: clippy
|
||||
|
||||
- name: Cache Rust dependencies
|
||||
uses: Swatinem/rust-cache@v2
|
||||
@@ -58,10 +57,13 @@ jobs:
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y build-essential cmake pkg-config libasound2-dev libclang-dev libjack-dev \
|
||||
libxcb-render0-dev libxcb-shape0-dev libxcb-xfixes0-dev libxkbcommon-dev libssl-dev libgl1-mesa-dev
|
||||
cargo install cargo-bundle
|
||||
|
||||
- name: Install dependencies (macOS)
|
||||
if: runner.os == 'macOS'
|
||||
run: brew list cmake &>/dev/null || brew install cmake
|
||||
run: |
|
||||
brew list cmake &>/dev/null || brew install cmake
|
||||
cargo install cargo-bundle
|
||||
|
||||
- name: Install dependencies (Windows)
|
||||
if: runner.os == 'Windows'
|
||||
@@ -75,11 +77,15 @@ jobs:
|
||||
- name: Build desktop
|
||||
run: cargo build --release --features desktop --bin cagire-desktop --target ${{ matrix.target }}
|
||||
|
||||
- name: Run tests
|
||||
run: cargo test --target ${{ matrix.target }}
|
||||
- name: Bundle desktop app
|
||||
if: runner.os != 'Windows'
|
||||
run: cargo bundle --release --features desktop --bin cagire-desktop --target ${{ matrix.target }}
|
||||
|
||||
- name: Run clippy
|
||||
run: cargo clippy --target ${{ matrix.target }} -- -D warnings
|
||||
- name: Zip macOS app bundle
|
||||
if: runner.os == 'macOS'
|
||||
run: |
|
||||
cd target/${{ matrix.target }}/release/bundle/osx
|
||||
zip -r Cagire.app.zip Cagire.app
|
||||
|
||||
- name: Upload artifact (Unix)
|
||||
if: runner.os != 'Windows'
|
||||
@@ -95,14 +101,21 @@ jobs:
|
||||
name: ${{ matrix.artifact }}
|
||||
path: target/${{ matrix.target }}/release/cagire.exe
|
||||
|
||||
- name: Upload desktop artifact (Unix)
|
||||
if: runner.os != 'Windows'
|
||||
- name: Upload desktop artifact (Linux deb)
|
||||
if: runner.os == 'Linux'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ matrix.artifact }}-desktop
|
||||
path: target/${{ matrix.target }}/release/cagire-desktop
|
||||
path: target/${{ matrix.target }}/release/bundle/deb/*.deb
|
||||
|
||||
- name: Upload desktop artifact (Windows)
|
||||
- name: Upload desktop artifact (macOS app bundle)
|
||||
if: runner.os == 'macOS'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ matrix.artifact }}-desktop
|
||||
path: target/${{ matrix.target }}/release/bundle/osx/Cagire.app.zip
|
||||
|
||||
- name: Upload desktop artifact (Windows exe)
|
||||
if: runner.os == 'Windows'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
@@ -130,10 +143,12 @@ jobs:
|
||||
name=$(basename "$dir")
|
||||
if [[ "$name" == *-desktop ]]; then
|
||||
base="${name%-desktop}"
|
||||
if [ -f "$dir/cagire-desktop.exe" ]; then
|
||||
if ls "$dir"/*.deb 1>/dev/null 2>&1; then
|
||||
cp "$dir"/*.deb "release/${base}-desktop.deb"
|
||||
elif [ -f "$dir/Cagire.app.zip" ]; then
|
||||
cp "$dir/Cagire.app.zip" "release/${base}-desktop.app.zip"
|
||||
elif [ -f "$dir/cagire-desktop.exe" ]; then
|
||||
cp "$dir/cagire-desktop.exe" "release/${base}-desktop.exe"
|
||||
elif [ -f "$dir/cagire-desktop" ]; then
|
||||
cp "$dir/cagire-desktop" "release/${base}-desktop"
|
||||
fi
|
||||
else
|
||||
if [ -f "$dir/cagire.exe" ]; then
|
||||
|
||||
22
.github/workflows/pages.yml
vendored
22
.github/workflows/pages.yml
vendored
@@ -25,13 +25,33 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 9
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
cache: pnpm
|
||||
cache-dependency-path: website/pnpm-lock.yaml
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install
|
||||
working-directory: website
|
||||
|
||||
- name: Build
|
||||
run: pnpm build
|
||||
working-directory: website
|
||||
|
||||
- name: Setup Pages
|
||||
uses: actions/configure-pages@v4
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-pages-artifact@v3
|
||||
with:
|
||||
path: website
|
||||
path: website/dist
|
||||
|
||||
- name: Deploy to GitHub Pages
|
||||
id: deployment
|
||||
|
||||
11
CHANGELOG.md
Normal file
11
CHANGELOG.md
Normal file
@@ -0,0 +1,11 @@
|
||||
# Changelog
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [0.0.2] - 2026-02-01
|
||||
- CI testing and codebase cleanup
|
||||
|
||||
## [0.0.1] - Initial Release
|
||||
- CI testing
|
||||
31
Cargo.toml
31
Cargo.toml
@@ -1,10 +1,24 @@
|
||||
[workspace]
|
||||
members = ["crates/forth", "crates/project", "crates/ratatui"]
|
||||
members = ["crates/forth", "crates/markdown", "crates/project", "crates/ratatui"]
|
||||
|
||||
[workspace.package]
|
||||
version = "0.0.2"
|
||||
edition = "2021"
|
||||
authors = ["Raphaël Forment <raphael.forment@gmail.com>"]
|
||||
license = "AGPL-3.0"
|
||||
repository = "https://github.com/Bubobubobubobubo/cagire"
|
||||
homepage = "https://cagire.raphaelforment.fr"
|
||||
description = "Forth-based live coding music sequencer"
|
||||
|
||||
[package]
|
||||
name = "cagire"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
authors.workspace = true
|
||||
license.workspace = true
|
||||
repository.workspace = true
|
||||
homepage.workspace = true
|
||||
description.workspace = true
|
||||
|
||||
[lib]
|
||||
name = "cagire"
|
||||
@@ -22,6 +36,7 @@ required-features = ["desktop"]
|
||||
[features]
|
||||
default = []
|
||||
desktop = [
|
||||
"cagire-forth/desktop",
|
||||
"egui",
|
||||
"eframe",
|
||||
"egui_ratatui",
|
||||
@@ -31,6 +46,7 @@ desktop = [
|
||||
|
||||
[dependencies]
|
||||
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"] }
|
||||
@@ -51,6 +67,7 @@ rustfft = "6"
|
||||
thread-priority = "1"
|
||||
ringbuf = "0.4"
|
||||
arc-swap = "1"
|
||||
midir = "0.10"
|
||||
|
||||
# Desktop-only dependencies (behind feature flag)
|
||||
egui = { version = "0.33", optional = true }
|
||||
@@ -65,3 +82,11 @@ lto = "fat"
|
||||
codegen-units = 1
|
||||
panic = "abort"
|
||||
strip = true
|
||||
|
||||
[package.metadata.bundle.bin.cagire-desktop]
|
||||
name = "Cagire"
|
||||
identifier = "com.sova.cagire"
|
||||
icon = ["assets/Cagire.icns", "assets/Cagire.ico"]
|
||||
copyright = "Copyright (c) 2025 Raphaël Forment"
|
||||
category = "Music"
|
||||
short_description = "Forth-based music sequencer"
|
||||
|
||||
BIN
assets/Cagire.icns
Normal file
BIN
assets/Cagire.icns
Normal file
Binary file not shown.
BIN
assets/Cagire.ico
Normal file
BIN
assets/Cagire.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 137 KiB |
@@ -1,7 +1,15 @@
|
||||
[package]
|
||||
name = "cagire-forth"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
authors.workspace = true
|
||||
license.workspace = true
|
||||
repository.workspace = true
|
||||
description = "Forth virtual machine for cagire sequencer"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
desktop = []
|
||||
|
||||
[dependencies]
|
||||
rand = "0.8"
|
||||
|
||||
@@ -61,7 +61,13 @@ fn tokenize(input: &str) -> Vec<Token> {
|
||||
continue;
|
||||
}
|
||||
// single ; is a word, create token
|
||||
tokens.push(Token::Word(";".to_string(), SourceSpan { start: pos, end: pos + 1 }));
|
||||
tokens.push(Token::Word(
|
||||
";".to_string(),
|
||||
SourceSpan {
|
||||
start: pos,
|
||||
end: pos + 1,
|
||||
},
|
||||
));
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -96,15 +102,33 @@ fn compile(tokens: &[Token], dict: &Dictionary) -> Result<Vec<Op>, String> {
|
||||
|
||||
while i < tokens.len() {
|
||||
match &tokens[i] {
|
||||
Token::Int(n, span) => ops.push(Op::PushInt(*n, Some(*span))),
|
||||
Token::Float(f, span) => ops.push(Op::PushFloat(*f, Some(*span))),
|
||||
Token::Int(n, span) => {
|
||||
let key = n.to_string();
|
||||
if let Some(body) = dict.lock().unwrap().get(&key).cloned() {
|
||||
ops.extend(body);
|
||||
} else {
|
||||
ops.push(Op::PushInt(*n, Some(*span)));
|
||||
}
|
||||
}
|
||||
Token::Float(f, span) => {
|
||||
let key = f.to_string();
|
||||
if let Some(body) = dict.lock().unwrap().get(&key).cloned() {
|
||||
ops.extend(body);
|
||||
} else {
|
||||
ops.push(Op::PushFloat(*f, Some(*span)));
|
||||
}
|
||||
}
|
||||
Token::Str(s, span) => ops.push(Op::PushStr(s.clone(), Some(*span))),
|
||||
Token::Word(w, span) => {
|
||||
let word = w.as_str();
|
||||
if word == "{" {
|
||||
let (quote_ops, consumed, end_span) = compile_quotation(&tokens[i + 1..], dict)?;
|
||||
let (quote_ops, consumed, end_span) =
|
||||
compile_quotation(&tokens[i + 1..], dict)?;
|
||||
i += consumed;
|
||||
let body_span = SourceSpan { start: span.start, end: end_span.end };
|
||||
let body_span = SourceSpan {
|
||||
start: span.start,
|
||||
end: end_span.end,
|
||||
};
|
||||
ops.push(Op::Quotation(quote_ops, Some(body_span)));
|
||||
} else if word == "}" {
|
||||
return Err("unexpected }".into());
|
||||
@@ -115,7 +139,8 @@ fn compile(tokens: &[Token], dict: &Dictionary) -> Result<Vec<Op>, String> {
|
||||
} else if word == ";" {
|
||||
return Err("unexpected ;".into());
|
||||
} else if word == "if" {
|
||||
let (then_ops, else_ops, consumed, then_span, else_span) = compile_if(&tokens[i + 1..], dict)?;
|
||||
let (then_ops, else_ops, consumed, then_span, else_span) =
|
||||
compile_if(&tokens[i + 1..], dict)?;
|
||||
i += consumed;
|
||||
if else_ops.is_empty() {
|
||||
ops.push(Op::BranchIfZero(then_ops.len(), then_span, None));
|
||||
@@ -137,7 +162,10 @@ fn compile(tokens: &[Token], dict: &Dictionary) -> Result<Vec<Op>, String> {
|
||||
Ok(ops)
|
||||
}
|
||||
|
||||
fn compile_quotation(tokens: &[Token], dict: &Dictionary) -> Result<(Vec<Op>, usize, SourceSpan), String> {
|
||||
fn compile_quotation(
|
||||
tokens: &[Token],
|
||||
dict: &Dictionary,
|
||||
) -> Result<(Vec<Op>, usize, SourceSpan), String> {
|
||||
let mut depth = 1;
|
||||
let mut end_idx = None;
|
||||
|
||||
@@ -172,13 +200,18 @@ fn token_span(tok: &Token) -> Option<SourceSpan> {
|
||||
}
|
||||
}
|
||||
|
||||
fn compile_colon_def(tokens: &[Token], dict: &Dictionary) -> Result<(usize, String, Vec<Op>), String> {
|
||||
fn compile_colon_def(
|
||||
tokens: &[Token],
|
||||
dict: &Dictionary,
|
||||
) -> Result<(usize, String, Vec<Op>), String> {
|
||||
if tokens.is_empty() {
|
||||
return Err("expected word name after ':'".into());
|
||||
}
|
||||
let name = match &tokens[0] {
|
||||
Token::Word(w, _) => w.clone(),
|
||||
_ => return Err("expected word name after ':'".into()),
|
||||
Token::Int(n, _) => n.to_string(),
|
||||
Token::Float(f, _) => f.to_string(),
|
||||
Token::Str(s, _) => s.clone(),
|
||||
};
|
||||
let mut semi_pos = None;
|
||||
for (i, tok) in tokens[1..].iter().enumerate() {
|
||||
@@ -198,11 +231,26 @@ fn compile_colon_def(tokens: &[Token], dict: &Dictionary) -> Result<(usize, Stri
|
||||
fn tokens_span(tokens: &[Token]) -> Option<SourceSpan> {
|
||||
let first = tokens.first().and_then(token_span)?;
|
||||
let last = tokens.last().and_then(token_span)?;
|
||||
Some(SourceSpan { start: first.start, end: last.end })
|
||||
Some(SourceSpan {
|
||||
start: first.start,
|
||||
end: last.end,
|
||||
})
|
||||
}
|
||||
|
||||
#[allow(clippy::type_complexity)]
|
||||
fn compile_if(tokens: &[Token], dict: &Dictionary) -> Result<(Vec<Op>, Vec<Op>, usize, Option<SourceSpan>, Option<SourceSpan>), String> {
|
||||
fn compile_if(
|
||||
tokens: &[Token],
|
||||
dict: &Dictionary,
|
||||
) -> Result<
|
||||
(
|
||||
Vec<Op>,
|
||||
Vec<Op>,
|
||||
usize,
|
||||
Option<SourceSpan>,
|
||||
Option<SourceSpan>,
|
||||
),
|
||||
String,
|
||||
> {
|
||||
let mut depth = 1;
|
||||
let mut else_pos = None;
|
||||
let mut then_pos = None;
|
||||
|
||||
@@ -5,6 +5,8 @@ mod types;
|
||||
mod vm;
|
||||
mod words;
|
||||
|
||||
pub use types::{Dictionary, ExecutionTrace, Rng, SourceSpan, StepContext, Value, Variables};
|
||||
pub use types::{
|
||||
CcAccess, Dictionary, ExecutionTrace, Rng, SourceSpan, StepContext, Value, Variables,
|
||||
};
|
||||
pub use vm::Forth;
|
||||
pub use words::{Word, WordCompile, WORDS};
|
||||
|
||||
@@ -79,11 +79,18 @@ pub enum Op {
|
||||
Loop,
|
||||
Degree(&'static [i64]),
|
||||
Oct,
|
||||
EmitN,
|
||||
ClearCmd,
|
||||
SetSpeed,
|
||||
At,
|
||||
IntRange,
|
||||
Generate,
|
||||
GeomRange,
|
||||
Times,
|
||||
// MIDI
|
||||
MidiEmit,
|
||||
GetMidiCC,
|
||||
MidiClock,
|
||||
MidiStart,
|
||||
MidiStop,
|
||||
MidiContinue,
|
||||
}
|
||||
|
||||
@@ -4,6 +4,12 @@ use std::sync::{Arc, Mutex};
|
||||
|
||||
use super::ops::Op;
|
||||
|
||||
/// Trait for accessing MIDI CC values. Implement this to provide CC memory to the Forth VM.
|
||||
pub trait CcAccess: Send + Sync {
|
||||
/// Get the CC value for a given device, channel (0-15), and CC number (0-127).
|
||||
fn get_cc(&self, device: usize, channel: usize, cc: usize) -> u8;
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
||||
pub struct SourceSpan {
|
||||
pub start: usize,
|
||||
@@ -29,6 +35,13 @@ pub struct StepContext {
|
||||
pub speed: f64,
|
||||
pub fill: bool,
|
||||
pub nudge_secs: f64,
|
||||
pub cc_access: Option<Arc<dyn CcAccess>>,
|
||||
#[cfg(feature = "desktop")]
|
||||
pub mouse_x: f64,
|
||||
#[cfg(feature = "desktop")]
|
||||
pub mouse_y: f64,
|
||||
#[cfg(feature = "desktop")]
|
||||
pub mouse_down: f64,
|
||||
}
|
||||
|
||||
impl StepContext {
|
||||
@@ -154,4 +167,3 @@ impl CmdRegister {
|
||||
self.params.clear();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -91,8 +91,11 @@ impl Forth {
|
||||
let mut pc = 0;
|
||||
let trace_cell = std::cell::RefCell::new(trace);
|
||||
|
||||
let run_quotation =
|
||||
|quot: Value, stack: &mut Vec<Value>, outputs: &mut Vec<String>, cmd: &mut CmdRegister| -> Result<(), String> {
|
||||
let run_quotation = |quot: Value,
|
||||
stack: &mut Vec<Value>,
|
||||
outputs: &mut Vec<String>,
|
||||
cmd: &mut CmdRegister|
|
||||
-> Result<(), String> {
|
||||
match quot {
|
||||
Value::Quotation(quot_ops, body_span) => {
|
||||
if let Some(span) = body_span {
|
||||
@@ -116,8 +119,11 @@ impl Forth {
|
||||
}
|
||||
};
|
||||
|
||||
let select_and_run =
|
||||
|selected: Value, stack: &mut Vec<Value>, outputs: &mut Vec<String>, cmd: &mut CmdRegister| -> Result<(), String> {
|
||||
let select_and_run = |selected: Value,
|
||||
stack: &mut Vec<Value>,
|
||||
outputs: &mut Vec<String>,
|
||||
cmd: &mut CmdRegister|
|
||||
-> Result<(), String> {
|
||||
if let Some(span) = selected.span() {
|
||||
if let Some(trace) = trace_cell.borrow_mut().as_mut() {
|
||||
trace.selected_spans.push(span);
|
||||
@@ -146,15 +152,20 @@ impl Forth {
|
||||
select_and_run(selected, stack, outputs, cmd)
|
||||
};
|
||||
|
||||
let emit_with_cycling = |cmd: &CmdRegister, emit_idx: usize, delta_secs: f64, outputs: &mut Vec<String>| -> Result<Option<Value>, String> {
|
||||
let emit_with_cycling = |cmd: &CmdRegister,
|
||||
emit_idx: usize,
|
||||
delta_secs: f64,
|
||||
outputs: &mut Vec<String>|
|
||||
-> Result<Option<Value>, String> {
|
||||
let (sound_opt, params) = cmd.snapshot().ok_or("nothing to emit")?;
|
||||
let resolved_sound_val = sound_opt.map(|sv| resolve_cycling(sv, emit_idx));
|
||||
let sound_str = match &resolved_sound_val {
|
||||
Some(v) => Some(v.as_str()?.to_string()),
|
||||
None => None,
|
||||
};
|
||||
let resolved_params: Vec<(String, String)> =
|
||||
params.iter().map(|(k, v)| {
|
||||
let resolved_params: Vec<(String, String)> = params
|
||||
.iter()
|
||||
.map(|(k, v)| {
|
||||
let resolved = resolve_cycling(v, emit_idx);
|
||||
if let Value::CycleList(_) = v {
|
||||
if let Some(span) = resolved.span() {
|
||||
@@ -164,8 +175,15 @@ impl Forth {
|
||||
}
|
||||
}
|
||||
(k.clone(), resolved.to_param_string())
|
||||
}).collect();
|
||||
emit_output(sound_str.as_deref(), &resolved_params, ctx.step_duration(), delta_secs, outputs);
|
||||
})
|
||||
.collect();
|
||||
emit_output(
|
||||
sound_str.as_deref(),
|
||||
&resolved_params,
|
||||
ctx.step_duration(),
|
||||
delta_secs,
|
||||
outputs,
|
||||
);
|
||||
Ok(resolved_sound_val.map(|v| v.into_owned()))
|
||||
};
|
||||
|
||||
@@ -369,7 +387,9 @@ impl Forth {
|
||||
trace.selected_spans.push(span);
|
||||
}
|
||||
}
|
||||
if let Some(sound_val) = emit_with_cycling(cmd, emit_idx, delta_secs, outputs)? {
|
||||
if let Some(sound_val) =
|
||||
emit_with_cycling(cmd, emit_idx, delta_secs, outputs)?
|
||||
{
|
||||
if let Some(span) = sound_val.span() {
|
||||
if let Some(trace) = trace_cell.borrow_mut().as_mut() {
|
||||
trace.selected_spans.push(span);
|
||||
@@ -407,6 +427,12 @@ impl Forth {
|
||||
"speed" => Value::Float(ctx.speed, None),
|
||||
"stepdur" => Value::Float(ctx.step_duration(), None),
|
||||
"fill" => Value::Int(if ctx.fill { 1 } else { 0 }, None),
|
||||
#[cfg(feature = "desktop")]
|
||||
"mx" => Value::Float(ctx.mouse_x, None),
|
||||
#[cfg(feature = "desktop")]
|
||||
"my" => Value::Float(ctx.mouse_y, None),
|
||||
#[cfg(feature = "desktop")]
|
||||
"mdown" => Value::Float(ctx.mouse_down, None),
|
||||
_ => Value::Int(0, None),
|
||||
};
|
||||
stack.push(val);
|
||||
@@ -417,7 +443,11 @@ impl Forth {
|
||||
let a = stack.pop().ok_or("stack underflow")?;
|
||||
match (&a, &b) {
|
||||
(Value::Int(a_i, _), Value::Int(b_i, _)) => {
|
||||
let (lo, hi) = if a_i <= b_i { (*a_i, *b_i) } else { (*b_i, *a_i) };
|
||||
let (lo, hi) = if a_i <= b_i {
|
||||
(*a_i, *b_i)
|
||||
} else {
|
||||
(*b_i, *a_i)
|
||||
};
|
||||
let val = self.rng.lock().unwrap().gen_range(lo..=hi);
|
||||
stack.push(Value::Int(val, None));
|
||||
}
|
||||
@@ -708,16 +738,6 @@ impl Forth {
|
||||
cmd.clear();
|
||||
}
|
||||
|
||||
Op::EmitN => {
|
||||
let n = stack.pop().ok_or("stack underflow")?.as_int()?;
|
||||
if n < 0 {
|
||||
return Err("emit count must be >= 0".into());
|
||||
}
|
||||
for i in 0..n as usize {
|
||||
emit_with_cycling(cmd, i, ctx.nudge_secs, outputs)?;
|
||||
}
|
||||
}
|
||||
|
||||
Op::IntRange => {
|
||||
let end = stack.pop().ok_or("stack underflow")?.as_int()?;
|
||||
let start = stack.pop().ok_or("stack underflow")?.as_int()?;
|
||||
@@ -748,6 +768,21 @@ impl Forth {
|
||||
}
|
||||
}
|
||||
|
||||
Op::Times => {
|
||||
let quot = stack.pop().ok_or("stack underflow")?;
|
||||
let count = stack.pop().ok_or("stack underflow")?.as_int()?;
|
||||
if count < 0 {
|
||||
return Err("times count must be >= 0".into());
|
||||
}
|
||||
for i in 0..count {
|
||||
self.vars
|
||||
.lock()
|
||||
.unwrap()
|
||||
.insert("i".to_string(), Value::Int(i, None));
|
||||
run_quotation(quot.clone(), stack, outputs, cmd)?;
|
||||
}
|
||||
}
|
||||
|
||||
Op::GeomRange => {
|
||||
let count = stack.pop().ok_or("stack underflow")?.as_int()?;
|
||||
let ratio = stack.pop().ok_or("stack underflow")?.as_float()?;
|
||||
@@ -761,6 +796,87 @@ impl Forth {
|
||||
val *= ratio;
|
||||
}
|
||||
}
|
||||
|
||||
// MIDI operations
|
||||
Op::MidiEmit => {
|
||||
let (_, params) = cmd.snapshot().unwrap_or((None, &[]));
|
||||
let get_int = |name: &str| -> Option<i64> {
|
||||
params
|
||||
.iter()
|
||||
.rev()
|
||||
.find(|(k, _)| k == name)
|
||||
.and_then(|(_, v)| v.as_int().ok())
|
||||
};
|
||||
let get_float = |name: &str| -> Option<f64> {
|
||||
params
|
||||
.iter()
|
||||
.rev()
|
||||
.find(|(k, _)| k == name)
|
||||
.and_then(|(_, v)| v.as_float().ok())
|
||||
};
|
||||
let chan = get_int("chan")
|
||||
.map(|c| (c.clamp(1, 16) - 1) as u8)
|
||||
.unwrap_or(0);
|
||||
let dev = get_int("dev").map(|d| d.clamp(0, 3) as u8).unwrap_or(0);
|
||||
|
||||
if let (Some(cc), Some(val)) = (get_int("ccnum"), get_int("ccout")) {
|
||||
let cc = cc.clamp(0, 127) as u8;
|
||||
let val = val.clamp(0, 127) as u8;
|
||||
outputs.push(format!("/midi/cc/{cc}/{val}/chan/{chan}/dev/{dev}"));
|
||||
} else if let Some(bend) = get_float("bend") {
|
||||
let bend_clamped = bend.clamp(-1.0, 1.0);
|
||||
let bend_14bit = ((bend_clamped + 1.0) * 8191.5) as u16;
|
||||
outputs.push(format!("/midi/bend/{bend_14bit}/chan/{chan}/dev/{dev}"));
|
||||
} else if let Some(pressure) = get_int("pressure") {
|
||||
let pressure = pressure.clamp(0, 127) as u8;
|
||||
outputs.push(format!("/midi/pressure/{pressure}/chan/{chan}/dev/{dev}"));
|
||||
} else if let Some(program) = get_int("program") {
|
||||
let program = program.clamp(0, 127) as u8;
|
||||
outputs.push(format!("/midi/program/{program}/chan/{chan}/dev/{dev}"));
|
||||
} else {
|
||||
let note = get_int("note").unwrap_or(60).clamp(0, 127) as u8;
|
||||
let velocity = get_int("velocity").unwrap_or(100).clamp(0, 127) as u8;
|
||||
let dur = get_float("dur").unwrap_or(1.0);
|
||||
let dur_secs = dur * ctx.step_duration();
|
||||
outputs.push(format!(
|
||||
"/midi/note/{note}/vel/{velocity}/chan/{chan}/dur/{dur_secs}/dev/{dev}"
|
||||
));
|
||||
}
|
||||
}
|
||||
Op::MidiClock => {
|
||||
let (_, params) = cmd.snapshot().unwrap_or((None, &[]));
|
||||
let dev = extract_dev_param(params);
|
||||
outputs.push(format!("/midi/clock/dev/{dev}"));
|
||||
}
|
||||
Op::MidiStart => {
|
||||
let (_, params) = cmd.snapshot().unwrap_or((None, &[]));
|
||||
let dev = extract_dev_param(params);
|
||||
outputs.push(format!("/midi/start/dev/{dev}"));
|
||||
}
|
||||
Op::MidiStop => {
|
||||
let (_, params) = cmd.snapshot().unwrap_or((None, &[]));
|
||||
let dev = extract_dev_param(params);
|
||||
outputs.push(format!("/midi/stop/dev/{dev}"));
|
||||
}
|
||||
Op::MidiContinue => {
|
||||
let (_, params) = cmd.snapshot().unwrap_or((None, &[]));
|
||||
let dev = extract_dev_param(params);
|
||||
outputs.push(format!("/midi/continue/dev/{dev}"));
|
||||
}
|
||||
Op::GetMidiCC => {
|
||||
let chan = stack.pop().ok_or("stack underflow")?.as_int()?;
|
||||
let cc = stack.pop().ok_or("stack underflow")?.as_int()?;
|
||||
let cc_clamped = (cc.clamp(0, 127)) as usize;
|
||||
let chan_clamped = (chan.clamp(1, 16) - 1) as usize;
|
||||
let (_, params) = cmd.snapshot().unwrap_or((None, &[]));
|
||||
let dev = extract_dev_param(params) as usize;
|
||||
let val = ctx
|
||||
.cc_access
|
||||
.as_ref()
|
||||
.map(|cc| cc.get_cc(dev, chan_clamped, cc_clamped))
|
||||
.unwrap_or(0);
|
||||
stack.push(Value::Int(val as i64, None));
|
||||
}
|
||||
}
|
||||
pc += 1;
|
||||
}
|
||||
@@ -769,6 +885,16 @@ impl Forth {
|
||||
}
|
||||
}
|
||||
|
||||
fn extract_dev_param(params: &[(String, Value)]) -> u8 {
|
||||
params
|
||||
.iter()
|
||||
.rev()
|
||||
.find(|(k, _)| k == "dev")
|
||||
.and_then(|(_, v)| v.as_int().ok())
|
||||
.map(|d| d.clamp(0, 3) as u8)
|
||||
.unwrap_or(0)
|
||||
}
|
||||
|
||||
fn is_tempo_scaled_param(name: &str) -> bool {
|
||||
matches!(
|
||||
name,
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
use std::collections::HashMap;
|
||||
use std::sync::LazyLock;
|
||||
|
||||
use super::ops::Op;
|
||||
use super::theory;
|
||||
use super::types::{Dictionary, SourceSpan};
|
||||
@@ -438,16 +441,6 @@ pub const WORDS: &[Word] = &[
|
||||
compile: Simple,
|
||||
varargs: false,
|
||||
},
|
||||
Word {
|
||||
name: ".!",
|
||||
aliases: &[],
|
||||
category: "Sound",
|
||||
stack: "(n --)",
|
||||
desc: "Emit current sound n times",
|
||||
example: "\"kick\" s 4 .!",
|
||||
compile: Simple,
|
||||
varargs: true,
|
||||
},
|
||||
// Variables (prefix syntax: @name to fetch, !name to store)
|
||||
Word {
|
||||
name: "@<var>",
|
||||
@@ -473,7 +466,7 @@ pub const WORDS: &[Word] = &[
|
||||
Word {
|
||||
name: "rand",
|
||||
aliases: &[],
|
||||
category: "Randomness",
|
||||
category: "Probability",
|
||||
stack: "(min max -- n|f)",
|
||||
desc: "Random in range. Int if both args are int, float otherwise",
|
||||
example: "1 6 rand => 4 | 0.0 1.0 rand => 0.42",
|
||||
@@ -483,7 +476,7 @@ pub const WORDS: &[Word] = &[
|
||||
Word {
|
||||
name: "seed",
|
||||
aliases: &[],
|
||||
category: "Randomness",
|
||||
category: "Probability",
|
||||
stack: "(n --)",
|
||||
desc: "Set random seed",
|
||||
example: "12345 seed",
|
||||
@@ -493,7 +486,7 @@ pub const WORDS: &[Word] = &[
|
||||
Word {
|
||||
name: "coin",
|
||||
aliases: &[],
|
||||
category: "Randomness",
|
||||
category: "Probability",
|
||||
stack: "(-- bool)",
|
||||
desc: "50/50 random boolean",
|
||||
example: "coin => 0 or 1",
|
||||
@@ -523,7 +516,7 @@ pub const WORDS: &[Word] = &[
|
||||
Word {
|
||||
name: "choose",
|
||||
aliases: &[],
|
||||
category: "Randomness",
|
||||
category: "Probability",
|
||||
stack: "(..n n -- val)",
|
||||
desc: "Random pick from n items",
|
||||
example: "1 2 3 3 choose",
|
||||
@@ -533,7 +526,7 @@ pub const WORDS: &[Word] = &[
|
||||
Word {
|
||||
name: "cycle",
|
||||
aliases: &[],
|
||||
category: "Selection",
|
||||
category: "Probability",
|
||||
stack: "(v1..vn n -- selected)",
|
||||
desc: "Cycle through n items by step runs",
|
||||
example: "60 64 67 3 cycle",
|
||||
@@ -543,7 +536,7 @@ pub const WORDS: &[Word] = &[
|
||||
Word {
|
||||
name: "pcycle",
|
||||
aliases: &[],
|
||||
category: "Selection",
|
||||
category: "Probability",
|
||||
stack: "(v1..vn n -- selected)",
|
||||
desc: "Cycle through n items by pattern iteration",
|
||||
example: "60 64 67 3 pcycle",
|
||||
@@ -553,7 +546,7 @@ pub const WORDS: &[Word] = &[
|
||||
Word {
|
||||
name: "tcycle",
|
||||
aliases: &[],
|
||||
category: "Selection",
|
||||
category: "Probability",
|
||||
stack: "(v1..vn n -- CycleList)",
|
||||
desc: "Create cycle list for emit-time resolution",
|
||||
example: "60 64 67 3 tcycle note",
|
||||
@@ -763,6 +756,39 @@ pub const WORDS: &[Word] = &[
|
||||
compile: Context("fill"),
|
||||
varargs: false,
|
||||
},
|
||||
#[cfg(feature = "desktop")]
|
||||
Word {
|
||||
name: "mx",
|
||||
aliases: &[],
|
||||
category: "Desktop",
|
||||
stack: "(-- x)",
|
||||
desc: "Normalized mouse X position (0-1)",
|
||||
example: "mx 440 880 range freq",
|
||||
compile: Context("mx"),
|
||||
varargs: false,
|
||||
},
|
||||
#[cfg(feature = "desktop")]
|
||||
Word {
|
||||
name: "my",
|
||||
aliases: &[],
|
||||
category: "Desktop",
|
||||
stack: "(-- y)",
|
||||
desc: "Normalized mouse Y position (0-1)",
|
||||
example: "my 0.1 0.9 range gain",
|
||||
compile: Context("my"),
|
||||
varargs: false,
|
||||
},
|
||||
#[cfg(feature = "desktop")]
|
||||
Word {
|
||||
name: "mdown",
|
||||
aliases: &[],
|
||||
category: "Desktop",
|
||||
stack: "(-- bool)",
|
||||
desc: "1 when mouse button held, 0 otherwise",
|
||||
example: "mdown { \"crash\" s . } ?",
|
||||
compile: Context("mdown"),
|
||||
varargs: false,
|
||||
},
|
||||
// Music
|
||||
Word {
|
||||
name: "mtof",
|
||||
@@ -1160,7 +1186,7 @@ pub const WORDS: &[Word] = &[
|
||||
Word {
|
||||
name: "gain",
|
||||
aliases: &[],
|
||||
category: "Gain",
|
||||
category: "Envelope",
|
||||
stack: "(f --)",
|
||||
desc: "Set volume (0-1)",
|
||||
example: "0.8 gain",
|
||||
@@ -1170,7 +1196,7 @@ pub const WORDS: &[Word] = &[
|
||||
Word {
|
||||
name: "postgain",
|
||||
aliases: &[],
|
||||
category: "Gain",
|
||||
category: "Envelope",
|
||||
stack: "(f --)",
|
||||
desc: "Set post gain",
|
||||
example: "1.2 postgain",
|
||||
@@ -1180,7 +1206,7 @@ pub const WORDS: &[Word] = &[
|
||||
Word {
|
||||
name: "velocity",
|
||||
aliases: &[],
|
||||
category: "Gain",
|
||||
category: "Envelope",
|
||||
stack: "(f --)",
|
||||
desc: "Set velocity",
|
||||
example: "100 velocity",
|
||||
@@ -1190,7 +1216,7 @@ pub const WORDS: &[Word] = &[
|
||||
Word {
|
||||
name: "pan",
|
||||
aliases: &[],
|
||||
category: "Gain",
|
||||
category: "Stereo",
|
||||
stack: "(f --)",
|
||||
desc: "Set pan (-1 to 1)",
|
||||
example: "0.5 pan",
|
||||
@@ -1470,7 +1496,7 @@ pub const WORDS: &[Word] = &[
|
||||
Word {
|
||||
name: "llpf",
|
||||
aliases: &[],
|
||||
category: "Ladder Filter",
|
||||
category: "Filter",
|
||||
stack: "(f --)",
|
||||
desc: "Set ladder lowpass frequency",
|
||||
example: "2000 llpf",
|
||||
@@ -1480,7 +1506,7 @@ pub const WORDS: &[Word] = &[
|
||||
Word {
|
||||
name: "llpq",
|
||||
aliases: &[],
|
||||
category: "Ladder Filter",
|
||||
category: "Filter",
|
||||
stack: "(f --)",
|
||||
desc: "Set ladder lowpass resonance",
|
||||
example: "0.5 llpq",
|
||||
@@ -1490,7 +1516,7 @@ pub const WORDS: &[Word] = &[
|
||||
Word {
|
||||
name: "lhpf",
|
||||
aliases: &[],
|
||||
category: "Ladder Filter",
|
||||
category: "Filter",
|
||||
stack: "(f --)",
|
||||
desc: "Set ladder highpass frequency",
|
||||
example: "100 lhpf",
|
||||
@@ -1500,7 +1526,7 @@ pub const WORDS: &[Word] = &[
|
||||
Word {
|
||||
name: "lhpq",
|
||||
aliases: &[],
|
||||
category: "Ladder Filter",
|
||||
category: "Filter",
|
||||
stack: "(f --)",
|
||||
desc: "Set ladder highpass resonance",
|
||||
example: "0.5 lhpq",
|
||||
@@ -1510,7 +1536,7 @@ pub const WORDS: &[Word] = &[
|
||||
Word {
|
||||
name: "lbpf",
|
||||
aliases: &[],
|
||||
category: "Ladder Filter",
|
||||
category: "Filter",
|
||||
stack: "(f --)",
|
||||
desc: "Set ladder bandpass frequency",
|
||||
example: "1000 lbpf",
|
||||
@@ -1520,7 +1546,7 @@ pub const WORDS: &[Word] = &[
|
||||
Word {
|
||||
name: "lbpq",
|
||||
aliases: &[],
|
||||
category: "Ladder Filter",
|
||||
category: "Filter",
|
||||
stack: "(f --)",
|
||||
desc: "Set ladder bandpass resonance",
|
||||
example: "0.5 lbpq",
|
||||
@@ -1540,7 +1566,7 @@ pub const WORDS: &[Word] = &[
|
||||
Word {
|
||||
name: "penv",
|
||||
aliases: &[],
|
||||
category: "Pitch Env",
|
||||
category: "Envelope",
|
||||
stack: "(f --)",
|
||||
desc: "Set pitch envelope",
|
||||
example: "0.5 penv",
|
||||
@@ -1550,7 +1576,7 @@ pub const WORDS: &[Word] = &[
|
||||
Word {
|
||||
name: "patt",
|
||||
aliases: &[],
|
||||
category: "Pitch Env",
|
||||
category: "Envelope",
|
||||
stack: "(f --)",
|
||||
desc: "Set pitch attack",
|
||||
example: "0.01 patt",
|
||||
@@ -1560,7 +1586,7 @@ pub const WORDS: &[Word] = &[
|
||||
Word {
|
||||
name: "pdec",
|
||||
aliases: &[],
|
||||
category: "Pitch Env",
|
||||
category: "Envelope",
|
||||
stack: "(f --)",
|
||||
desc: "Set pitch decay",
|
||||
example: "0.1 pdec",
|
||||
@@ -1570,7 +1596,7 @@ pub const WORDS: &[Word] = &[
|
||||
Word {
|
||||
name: "psus",
|
||||
aliases: &[],
|
||||
category: "Pitch Env",
|
||||
category: "Envelope",
|
||||
stack: "(f --)",
|
||||
desc: "Set pitch sustain",
|
||||
example: "0 psus",
|
||||
@@ -1580,7 +1606,7 @@ pub const WORDS: &[Word] = &[
|
||||
Word {
|
||||
name: "prel",
|
||||
aliases: &[],
|
||||
category: "Pitch Env",
|
||||
category: "Envelope",
|
||||
stack: "(f --)",
|
||||
desc: "Set pitch release",
|
||||
example: "0.1 prel",
|
||||
@@ -1620,7 +1646,7 @@ pub const WORDS: &[Word] = &[
|
||||
Word {
|
||||
name: "fm",
|
||||
aliases: &[],
|
||||
category: "Modulation",
|
||||
category: "FM",
|
||||
stack: "(f --)",
|
||||
desc: "Set FM frequency",
|
||||
example: "200 fm",
|
||||
@@ -1630,7 +1656,7 @@ pub const WORDS: &[Word] = &[
|
||||
Word {
|
||||
name: "fmh",
|
||||
aliases: &[],
|
||||
category: "Modulation",
|
||||
category: "FM",
|
||||
stack: "(f --)",
|
||||
desc: "Set FM harmonic ratio",
|
||||
example: "2 fmh",
|
||||
@@ -1640,7 +1666,7 @@ pub const WORDS: &[Word] = &[
|
||||
Word {
|
||||
name: "fmshape",
|
||||
aliases: &[],
|
||||
category: "Modulation",
|
||||
category: "FM",
|
||||
stack: "(f --)",
|
||||
desc: "Set FM shape",
|
||||
example: "0 fmshape",
|
||||
@@ -1650,7 +1676,7 @@ pub const WORDS: &[Word] = &[
|
||||
Word {
|
||||
name: "fme",
|
||||
aliases: &[],
|
||||
category: "Modulation",
|
||||
category: "FM",
|
||||
stack: "(f --)",
|
||||
desc: "Set FM envelope",
|
||||
example: "0.5 fme",
|
||||
@@ -1660,7 +1686,7 @@ pub const WORDS: &[Word] = &[
|
||||
Word {
|
||||
name: "fma",
|
||||
aliases: &[],
|
||||
category: "Modulation",
|
||||
category: "FM",
|
||||
stack: "(f --)",
|
||||
desc: "Set FM attack",
|
||||
example: "0.01 fma",
|
||||
@@ -1670,7 +1696,7 @@ pub const WORDS: &[Word] = &[
|
||||
Word {
|
||||
name: "fmd",
|
||||
aliases: &[],
|
||||
category: "Modulation",
|
||||
category: "FM",
|
||||
stack: "(f --)",
|
||||
desc: "Set FM decay",
|
||||
example: "0.1 fmd",
|
||||
@@ -1680,7 +1706,7 @@ pub const WORDS: &[Word] = &[
|
||||
Word {
|
||||
name: "fms",
|
||||
aliases: &[],
|
||||
category: "Modulation",
|
||||
category: "FM",
|
||||
stack: "(f --)",
|
||||
desc: "Set FM sustain",
|
||||
example: "0.5 fms",
|
||||
@@ -1690,7 +1716,7 @@ pub const WORDS: &[Word] = &[
|
||||
Word {
|
||||
name: "fmr",
|
||||
aliases: &[],
|
||||
category: "Modulation",
|
||||
category: "FM",
|
||||
stack: "(f --)",
|
||||
desc: "Set FM release",
|
||||
example: "0.1 fmr",
|
||||
@@ -1860,7 +1886,7 @@ pub const WORDS: &[Word] = &[
|
||||
Word {
|
||||
name: "eqlo",
|
||||
aliases: &[],
|
||||
category: "EQ",
|
||||
category: "Filter",
|
||||
stack: "(f --)",
|
||||
desc: "Set low shelf gain (dB)",
|
||||
example: "3 eqlo",
|
||||
@@ -1870,7 +1896,7 @@ pub const WORDS: &[Word] = &[
|
||||
Word {
|
||||
name: "eqmid",
|
||||
aliases: &[],
|
||||
category: "EQ",
|
||||
category: "Filter",
|
||||
stack: "(f --)",
|
||||
desc: "Set mid peak gain (dB)",
|
||||
example: "-2 eqmid",
|
||||
@@ -1880,7 +1906,7 @@ pub const WORDS: &[Word] = &[
|
||||
Word {
|
||||
name: "eqhi",
|
||||
aliases: &[],
|
||||
category: "EQ",
|
||||
category: "Filter",
|
||||
stack: "(f --)",
|
||||
desc: "Set high shelf gain (dB)",
|
||||
example: "1 eqhi",
|
||||
@@ -1890,7 +1916,7 @@ pub const WORDS: &[Word] = &[
|
||||
Word {
|
||||
name: "tilt",
|
||||
aliases: &[],
|
||||
category: "EQ",
|
||||
category: "Filter",
|
||||
stack: "(f --)",
|
||||
desc: "Set tilt EQ (-1 dark, 1 bright)",
|
||||
example: "-0.5 tilt",
|
||||
@@ -2280,8 +2306,164 @@ pub const WORDS: &[Word] = &[
|
||||
compile: Simple,
|
||||
varargs: false,
|
||||
},
|
||||
Word {
|
||||
name: "times",
|
||||
aliases: &[],
|
||||
category: "Control",
|
||||
stack: "(n quot --)",
|
||||
desc: "Execute quotation n times, @i holds current index",
|
||||
example: "4 { @i . } times => 0 1 2 3",
|
||||
compile: Simple,
|
||||
varargs: false,
|
||||
},
|
||||
// MIDI
|
||||
Word {
|
||||
name: "chan",
|
||||
aliases: &[],
|
||||
category: "MIDI",
|
||||
stack: "(n --)",
|
||||
desc: "Set MIDI channel 1-16",
|
||||
example: "1 chan",
|
||||
compile: Param,
|
||||
varargs: false,
|
||||
},
|
||||
Word {
|
||||
name: "ccnum",
|
||||
aliases: &[],
|
||||
category: "MIDI",
|
||||
stack: "(n --)",
|
||||
desc: "Set MIDI CC number 0-127",
|
||||
example: "1 ccnum",
|
||||
compile: Param,
|
||||
varargs: false,
|
||||
},
|
||||
Word {
|
||||
name: "ccout",
|
||||
aliases: &[],
|
||||
category: "MIDI",
|
||||
stack: "(n --)",
|
||||
desc: "Set MIDI CC output value 0-127",
|
||||
example: "64 ccout",
|
||||
compile: Param,
|
||||
varargs: false,
|
||||
},
|
||||
Word {
|
||||
name: "bend",
|
||||
aliases: &[],
|
||||
category: "MIDI",
|
||||
stack: "(f --)",
|
||||
desc: "Set pitch bend -1.0 to 1.0 (0 = center)",
|
||||
example: "0.5 bend",
|
||||
compile: Param,
|
||||
varargs: false,
|
||||
},
|
||||
Word {
|
||||
name: "pressure",
|
||||
aliases: &[],
|
||||
category: "MIDI",
|
||||
stack: "(n --)",
|
||||
desc: "Set channel pressure (aftertouch) 0-127",
|
||||
example: "64 pressure",
|
||||
compile: Param,
|
||||
varargs: false,
|
||||
},
|
||||
Word {
|
||||
name: "program",
|
||||
aliases: &[],
|
||||
category: "MIDI",
|
||||
stack: "(n --)",
|
||||
desc: "Set program change number 0-127",
|
||||
example: "0 program",
|
||||
compile: Param,
|
||||
varargs: false,
|
||||
},
|
||||
Word {
|
||||
name: "m.",
|
||||
aliases: &[],
|
||||
category: "MIDI",
|
||||
stack: "(--)",
|
||||
desc: "Emit MIDI message from params (note/cc/bend/pressure/program)",
|
||||
example: "60 note 100 velocity 1 chan m.",
|
||||
compile: Simple,
|
||||
varargs: false,
|
||||
},
|
||||
Word {
|
||||
name: "mclock",
|
||||
aliases: &[],
|
||||
category: "MIDI",
|
||||
stack: "(--)",
|
||||
desc: "Send MIDI clock pulse (24 per quarter note)",
|
||||
example: "mclock",
|
||||
compile: Simple,
|
||||
varargs: false,
|
||||
},
|
||||
Word {
|
||||
name: "mstart",
|
||||
aliases: &[],
|
||||
category: "MIDI",
|
||||
stack: "(--)",
|
||||
desc: "Send MIDI start message",
|
||||
example: "mstart",
|
||||
compile: Simple,
|
||||
varargs: false,
|
||||
},
|
||||
Word {
|
||||
name: "mstop",
|
||||
aliases: &[],
|
||||
category: "MIDI",
|
||||
stack: "(--)",
|
||||
desc: "Send MIDI stop message",
|
||||
example: "mstop",
|
||||
compile: Simple,
|
||||
varargs: false,
|
||||
},
|
||||
Word {
|
||||
name: "mcont",
|
||||
aliases: &[],
|
||||
category: "MIDI",
|
||||
stack: "(--)",
|
||||
desc: "Send MIDI continue message",
|
||||
example: "mcont",
|
||||
compile: Simple,
|
||||
varargs: false,
|
||||
},
|
||||
Word {
|
||||
name: "ccval",
|
||||
aliases: &[],
|
||||
category: "MIDI",
|
||||
stack: "(cc chan -- val)",
|
||||
desc: "Read CC value 0-127 from MIDI input (uses dev param for device)",
|
||||
example: "1 1 ccval",
|
||||
compile: Simple,
|
||||
varargs: false,
|
||||
},
|
||||
Word {
|
||||
name: "dev",
|
||||
aliases: &[],
|
||||
category: "MIDI",
|
||||
stack: "(n --)",
|
||||
desc: "Set MIDI device slot 0-3 for output/input",
|
||||
example: "1 dev 60 note m.",
|
||||
compile: Param,
|
||||
varargs: false,
|
||||
},
|
||||
];
|
||||
|
||||
static WORD_MAP: LazyLock<HashMap<&'static str, &'static Word>> = LazyLock::new(|| {
|
||||
let mut map = HashMap::with_capacity(WORDS.len() * 2);
|
||||
for word in WORDS {
|
||||
map.insert(word.name, word);
|
||||
for alias in word.aliases {
|
||||
map.insert(alias, word);
|
||||
}
|
||||
}
|
||||
map
|
||||
});
|
||||
|
||||
fn lookup_word(name: &str) -> Option<&'static Word> {
|
||||
WORD_MAP.get(name).copied()
|
||||
}
|
||||
|
||||
pub(super) fn simple_op(name: &str) -> Option<Op> {
|
||||
Some(match name {
|
||||
"dup" => Op::Dup,
|
||||
@@ -2352,11 +2534,17 @@ pub(super) fn simple_op(name: &str) -> Option<Op> {
|
||||
"chain" => Op::Chain,
|
||||
"loop" => Op::Loop,
|
||||
"oct" => Op::Oct,
|
||||
".!" => Op::EmitN,
|
||||
"clear" => Op::ClearCmd,
|
||||
".." => Op::IntRange,
|
||||
"gen" => Op::Generate,
|
||||
"geom.." => Op::GeomRange,
|
||||
"times" => Op::Times,
|
||||
"m." => Op::MidiEmit,
|
||||
"ccval" => Op::GetMidiCC,
|
||||
"mclock" => Op::MidiClock,
|
||||
"mstart" => Op::MidiStart,
|
||||
"mstop" => Op::MidiStop,
|
||||
"mcont" => Op::MidiContinue,
|
||||
_ => return None,
|
||||
})
|
||||
}
|
||||
@@ -2466,8 +2654,7 @@ pub(super) fn compile_word(
|
||||
return true;
|
||||
}
|
||||
|
||||
for word in WORDS {
|
||||
if word.name == name || word.aliases.contains(&name) {
|
||||
if let Some(word) = lookup_word(name) {
|
||||
match &word.compile {
|
||||
Simple => {
|
||||
if let Some(op) = simple_op(word.name) {
|
||||
@@ -2483,7 +2670,6 @@ pub(super) fn compile_word(
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// @varname - fetch variable
|
||||
if let Some(var_name) = name.strip_prefix('@') {
|
||||
|
||||
12
crates/markdown/Cargo.toml
Normal file
12
crates/markdown/Cargo.toml
Normal file
@@ -0,0 +1,12 @@
|
||||
[package]
|
||||
name = "cagire-markdown"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
authors.workspace = true
|
||||
license.workspace = true
|
||||
repository.workspace = true
|
||||
description = "Markdown rendering for cagire sequencer"
|
||||
|
||||
[dependencies]
|
||||
minimad = "0.13"
|
||||
ratatui = "0.30"
|
||||
13
crates/markdown/src/highlighter.rs
Normal file
13
crates/markdown/src/highlighter.rs
Normal file
@@ -0,0 +1,13 @@
|
||||
use ratatui::style::Style;
|
||||
|
||||
pub trait CodeHighlighter {
|
||||
fn highlight(&self, line: &str) -> Vec<(Style, String)>;
|
||||
}
|
||||
|
||||
pub struct NoHighlight;
|
||||
|
||||
impl CodeHighlighter for NoHighlight {
|
||||
fn highlight(&self, line: &str) -> Vec<(Style, String)> {
|
||||
vec![(Style::default(), line.to_string())]
|
||||
}
|
||||
}
|
||||
7
crates/markdown/src/lib.rs
Normal file
7
crates/markdown/src/lib.rs
Normal file
@@ -0,0 +1,7 @@
|
||||
mod highlighter;
|
||||
mod parser;
|
||||
mod theme;
|
||||
|
||||
pub use highlighter::{CodeHighlighter, NoHighlight};
|
||||
pub use parser::parse;
|
||||
pub use theme::{DefaultTheme, MarkdownTheme};
|
||||
327
crates/markdown/src/parser.rs
Normal file
327
crates/markdown/src/parser.rs
Normal file
@@ -0,0 +1,327 @@
|
||||
use minimad::{Composite, CompositeStyle, Compound, Line, TableRow};
|
||||
use ratatui::style::{Modifier, Style};
|
||||
use ratatui::text::{Line as RLine, Span};
|
||||
|
||||
use crate::highlighter::CodeHighlighter;
|
||||
use crate::theme::MarkdownTheme;
|
||||
|
||||
pub fn parse<T: MarkdownTheme, H: CodeHighlighter>(
|
||||
md: &str,
|
||||
theme: &T,
|
||||
highlighter: &H,
|
||||
) -> Vec<RLine<'static>> {
|
||||
let processed = preprocess_markdown(md);
|
||||
let text = minimad::Text::from(processed.as_str());
|
||||
let mut lines = Vec::new();
|
||||
|
||||
let mut code_line_nr: usize = 0;
|
||||
let mut table_buffer: Vec<TableRow> = Vec::new();
|
||||
|
||||
let flush_table = |buf: &mut Vec<TableRow>, out: &mut Vec<RLine<'static>>, theme: &T| {
|
||||
if buf.is_empty() {
|
||||
return;
|
||||
}
|
||||
let col_widths = compute_column_widths(buf);
|
||||
for (row_idx, row) in buf.drain(..).enumerate() {
|
||||
out.push(render_table_row(row, row_idx, &col_widths, theme));
|
||||
}
|
||||
};
|
||||
|
||||
for line in text.lines {
|
||||
match line {
|
||||
Line::Normal(composite) if composite.style == CompositeStyle::Code => {
|
||||
flush_table(&mut table_buffer, &mut lines, theme);
|
||||
code_line_nr += 1;
|
||||
let raw: String = composite
|
||||
.compounds
|
||||
.iter()
|
||||
.map(|c: &minimad::Compound| c.src)
|
||||
.collect();
|
||||
let mut spans = vec![
|
||||
Span::styled(format!(" {code_line_nr:>2} "), theme.code_border()),
|
||||
Span::styled("│ ", theme.code_border()),
|
||||
];
|
||||
spans.extend(
|
||||
highlighter
|
||||
.highlight(&raw)
|
||||
.into_iter()
|
||||
.map(|(style, text)| Span::styled(text, style)),
|
||||
);
|
||||
lines.push(RLine::from(spans));
|
||||
}
|
||||
Line::Normal(composite) => {
|
||||
flush_table(&mut table_buffer, &mut lines, theme);
|
||||
code_line_nr = 0;
|
||||
lines.push(composite_to_line(composite, theme));
|
||||
}
|
||||
Line::TableRow(row) => {
|
||||
code_line_nr = 0;
|
||||
table_buffer.push(row);
|
||||
}
|
||||
Line::TableRule(_) => {}
|
||||
_ => {
|
||||
flush_table(&mut table_buffer, &mut lines, theme);
|
||||
code_line_nr = 0;
|
||||
lines.push(RLine::from(""));
|
||||
}
|
||||
}
|
||||
}
|
||||
flush_table(&mut table_buffer, &mut lines, theme);
|
||||
|
||||
lines
|
||||
}
|
||||
|
||||
pub fn preprocess_markdown(md: &str) -> String {
|
||||
let mut out = String::with_capacity(md.len());
|
||||
for line in md.lines() {
|
||||
let line = convert_dash_lists(line);
|
||||
let mut result = String::with_capacity(line.len());
|
||||
let mut chars = line.char_indices().peekable();
|
||||
let bytes = line.as_bytes();
|
||||
while let Some((i, c)) = chars.next() {
|
||||
if c == '`' {
|
||||
result.push(c);
|
||||
for (_, ch) in chars.by_ref() {
|
||||
result.push(ch);
|
||||
if ch == '`' {
|
||||
break;
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if c == '_' {
|
||||
let before_is_space = i == 0 || bytes[i - 1] == b' ';
|
||||
if before_is_space {
|
||||
if let Some(end) = line[i + 1..].find('_') {
|
||||
let inner = &line[i + 1..i + 1 + end];
|
||||
if !inner.is_empty() {
|
||||
result.push('*');
|
||||
result.push_str(inner);
|
||||
result.push('*');
|
||||
for _ in 0..end {
|
||||
chars.next();
|
||||
}
|
||||
chars.next();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
result.push(c);
|
||||
}
|
||||
out.push_str(&result);
|
||||
out.push('\n');
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
pub fn convert_dash_lists(line: &str) -> String {
|
||||
let trimmed = line.trim_start();
|
||||
if let Some(rest) = trimmed.strip_prefix("- ") {
|
||||
let indent = line.len() - trimmed.len();
|
||||
format!("{}* {}", " ".repeat(indent), rest)
|
||||
} else {
|
||||
line.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
fn cell_text_width(cell: &Composite) -> usize {
|
||||
cell.compounds.iter().map(|c| c.src.chars().count()).sum()
|
||||
}
|
||||
|
||||
fn compute_column_widths(rows: &[TableRow]) -> Vec<usize> {
|
||||
let mut widths: Vec<usize> = Vec::new();
|
||||
for row in rows {
|
||||
for (i, cell) in row.cells.iter().enumerate() {
|
||||
let w = cell_text_width(cell);
|
||||
if i >= widths.len() {
|
||||
widths.push(w);
|
||||
} else if w > widths[i] {
|
||||
widths[i] = w;
|
||||
}
|
||||
}
|
||||
}
|
||||
widths
|
||||
}
|
||||
|
||||
fn render_table_row<T: MarkdownTheme>(
|
||||
row: TableRow,
|
||||
row_idx: usize,
|
||||
col_widths: &[usize],
|
||||
theme: &T,
|
||||
) -> RLine<'static> {
|
||||
let is_header = row_idx == 0;
|
||||
let bg = if is_header {
|
||||
theme.table_header_bg()
|
||||
} else if row_idx.is_multiple_of(2) {
|
||||
theme.table_row_even()
|
||||
} else {
|
||||
theme.table_row_odd()
|
||||
};
|
||||
|
||||
let base_style = if is_header {
|
||||
theme.text().bg(bg).add_modifier(Modifier::BOLD)
|
||||
} else {
|
||||
theme.text().bg(bg)
|
||||
};
|
||||
|
||||
let sep_style = theme.code_border().bg(bg);
|
||||
let mut spans: Vec<Span<'static>> = Vec::new();
|
||||
|
||||
for (i, cell) in row.cells.into_iter().enumerate() {
|
||||
if i > 0 {
|
||||
spans.push(Span::styled(" │ ", sep_style));
|
||||
}
|
||||
let target_width = col_widths.get(i).copied().unwrap_or(0);
|
||||
let cell_width = cell
|
||||
.compounds
|
||||
.iter()
|
||||
.map(|c| c.src.chars().count())
|
||||
.sum::<usize>();
|
||||
|
||||
for compound in cell.compounds {
|
||||
compound_to_spans(compound, base_style, &mut spans, theme);
|
||||
}
|
||||
|
||||
let padding = target_width.saturating_sub(cell_width);
|
||||
if padding > 0 {
|
||||
spans.push(Span::styled(" ".repeat(padding), base_style));
|
||||
}
|
||||
}
|
||||
|
||||
RLine::from(spans)
|
||||
}
|
||||
|
||||
fn composite_to_line<T: MarkdownTheme>(composite: Composite, theme: &T) -> RLine<'static> {
|
||||
let base_style = match composite.style {
|
||||
CompositeStyle::Header(1) => theme.h1(),
|
||||
CompositeStyle::Header(2) => theme.h2(),
|
||||
CompositeStyle::Header(_) => theme.h3(),
|
||||
CompositeStyle::ListItem(_) => theme.list(),
|
||||
CompositeStyle::Quote => theme.quote(),
|
||||
CompositeStyle::Code => theme.code(),
|
||||
CompositeStyle::Paragraph => theme.text(),
|
||||
};
|
||||
|
||||
let prefix: String = match composite.style {
|
||||
CompositeStyle::ListItem(depth) => {
|
||||
let indent = " ".repeat(depth as usize);
|
||||
format!("{indent}• ")
|
||||
}
|
||||
CompositeStyle::Quote => " │ ".to_string(),
|
||||
_ => String::new(),
|
||||
};
|
||||
|
||||
let mut spans: Vec<Span<'static>> = Vec::new();
|
||||
if !prefix.is_empty() {
|
||||
spans.push(Span::styled(prefix, base_style));
|
||||
}
|
||||
|
||||
for compound in composite.compounds {
|
||||
compound_to_spans(compound, base_style, &mut spans, theme);
|
||||
}
|
||||
|
||||
RLine::from(spans)
|
||||
}
|
||||
|
||||
fn compound_to_spans<T: MarkdownTheme>(
|
||||
compound: Compound,
|
||||
base: Style,
|
||||
out: &mut Vec<Span<'static>>,
|
||||
theme: &T,
|
||||
) {
|
||||
let mut style = base;
|
||||
|
||||
if compound.bold {
|
||||
style = style.add_modifier(Modifier::BOLD);
|
||||
}
|
||||
if compound.italic {
|
||||
style = style.add_modifier(Modifier::ITALIC);
|
||||
}
|
||||
if compound.code {
|
||||
style = theme.code();
|
||||
}
|
||||
if compound.strikeout {
|
||||
style = style.add_modifier(Modifier::CROSSED_OUT);
|
||||
}
|
||||
|
||||
let src = compound.src.to_string();
|
||||
let link_style = theme.link();
|
||||
|
||||
let mut rest = src.as_str();
|
||||
while let Some(start) = rest.find('[') {
|
||||
let after_bracket = &rest[start + 1..];
|
||||
if let Some(text_end) = after_bracket.find("](") {
|
||||
let url_start = start + 1 + text_end + 2;
|
||||
if let Some(url_end) = rest[url_start..].find(')') {
|
||||
if start > 0 {
|
||||
out.push(Span::styled(rest[..start].to_string(), style));
|
||||
}
|
||||
let text = &rest[start + 1..start + 1 + text_end];
|
||||
let url = &rest[url_start..url_start + url_end];
|
||||
if text == url {
|
||||
out.push(Span::styled(url.to_string(), link_style));
|
||||
} else {
|
||||
out.push(Span::styled(text.to_string(), link_style));
|
||||
out.push(Span::styled(format!(" ({url})"), theme.link_url()));
|
||||
}
|
||||
rest = &rest[url_start + url_end + 1..];
|
||||
continue;
|
||||
}
|
||||
}
|
||||
out.push(Span::styled(rest[..start + 1].to_string(), style));
|
||||
rest = &rest[start + 1..];
|
||||
}
|
||||
if !rest.is_empty() {
|
||||
out.push(Span::styled(rest.to_string(), style));
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::highlighter::NoHighlight;
|
||||
use crate::theme::DefaultTheme;
|
||||
|
||||
#[test]
|
||||
fn test_preprocess_underscores() {
|
||||
assert_eq!(preprocess_markdown("_italic_"), "*italic*\n");
|
||||
assert_eq!(preprocess_markdown("word_with_underscores"), "word_with_underscores\n");
|
||||
assert_eq!(preprocess_markdown("hello _world_"), "hello *world*\n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_preprocess_dash_lists() {
|
||||
assert_eq!(convert_dash_lists("- item"), "* item");
|
||||
assert_eq!(convert_dash_lists(" - nested"), " * nested");
|
||||
assert_eq!(convert_dash_lists("not-a-list"), "not-a-list");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_headings() {
|
||||
let md = "# H1\n## H2\n### H3";
|
||||
let lines = parse(md, &DefaultTheme, &NoHighlight);
|
||||
assert_eq!(lines.len(), 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_code_block() {
|
||||
let md = "```\ncode line\n```";
|
||||
let lines = parse(md, &DefaultTheme, &NoHighlight);
|
||||
assert!(!lines.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_table() {
|
||||
let md = "| A | B |\n|---|---|\n| 1 | 2 |";
|
||||
let lines = parse(md, &DefaultTheme, &NoHighlight);
|
||||
assert_eq!(lines.len(), 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_default_theme_works() {
|
||||
let md = "Hello **world**";
|
||||
let lines = parse(md, &DefaultTheme, &NoHighlight);
|
||||
assert_eq!(lines.len(), 1);
|
||||
}
|
||||
}
|
||||
77
crates/markdown/src/theme.rs
Normal file
77
crates/markdown/src/theme.rs
Normal file
@@ -0,0 +1,77 @@
|
||||
use ratatui::style::{Color, Modifier, Style};
|
||||
|
||||
pub trait MarkdownTheme {
|
||||
fn h1(&self) -> Style;
|
||||
fn h2(&self) -> Style;
|
||||
fn h3(&self) -> Style;
|
||||
fn text(&self) -> Style;
|
||||
fn code(&self) -> Style;
|
||||
fn code_border(&self) -> Style;
|
||||
fn link(&self) -> Style;
|
||||
fn link_url(&self) -> Style;
|
||||
fn quote(&self) -> Style;
|
||||
fn list(&self) -> Style;
|
||||
fn table_header_bg(&self) -> Color;
|
||||
fn table_row_even(&self) -> Color;
|
||||
fn table_row_odd(&self) -> Color;
|
||||
}
|
||||
|
||||
pub struct DefaultTheme;
|
||||
|
||||
impl MarkdownTheme for DefaultTheme {
|
||||
fn h1(&self) -> Style {
|
||||
Style::new()
|
||||
.fg(Color::Cyan)
|
||||
.add_modifier(Modifier::BOLD | Modifier::UNDERLINED)
|
||||
}
|
||||
|
||||
fn h2(&self) -> Style {
|
||||
Style::new().fg(Color::Blue).add_modifier(Modifier::BOLD)
|
||||
}
|
||||
|
||||
fn h3(&self) -> Style {
|
||||
Style::new().fg(Color::Magenta).add_modifier(Modifier::BOLD)
|
||||
}
|
||||
|
||||
fn text(&self) -> Style {
|
||||
Style::new().fg(Color::White)
|
||||
}
|
||||
|
||||
fn code(&self) -> Style {
|
||||
Style::new().fg(Color::Yellow)
|
||||
}
|
||||
|
||||
fn code_border(&self) -> Style {
|
||||
Style::new().fg(Color::DarkGray)
|
||||
}
|
||||
|
||||
fn link(&self) -> Style {
|
||||
Style::new()
|
||||
.fg(Color::Blue)
|
||||
.add_modifier(Modifier::UNDERLINED)
|
||||
}
|
||||
|
||||
fn link_url(&self) -> Style {
|
||||
Style::new().fg(Color::DarkGray)
|
||||
}
|
||||
|
||||
fn quote(&self) -> Style {
|
||||
Style::new().fg(Color::Gray)
|
||||
}
|
||||
|
||||
fn list(&self) -> Style {
|
||||
Style::new().fg(Color::White)
|
||||
}
|
||||
|
||||
fn table_header_bg(&self) -> Color {
|
||||
Color::DarkGray
|
||||
}
|
||||
|
||||
fn table_row_even(&self) -> Color {
|
||||
Color::Reset
|
||||
}
|
||||
|
||||
fn table_row_odd(&self) -> Color {
|
||||
Color::Reset
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,11 @@
|
||||
[package]
|
||||
name = "cagire-project"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
authors.workspace = true
|
||||
license.workspace = true
|
||||
repository.workspace = true
|
||||
description = "Project data structures for cagire sequencer"
|
||||
|
||||
[dependencies]
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
[package]
|
||||
name = "cagire-ratatui"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
authors.workspace = true
|
||||
license.workspace = true
|
||||
repository.workspace = true
|
||||
description = "TUI components for cagire sequencer"
|
||||
|
||||
[dependencies]
|
||||
rand = "0.8"
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
use crate::theme::{browser, input, ui};
|
||||
use ratatui::style::Color;
|
||||
use crate::theme;
|
||||
use ratatui::layout::{Constraint, Layout, Rect};
|
||||
use ratatui::style::Style;
|
||||
use ratatui::style::{Color, Style};
|
||||
use ratatui::text::{Line, Span};
|
||||
use ratatui::widgets::Paragraph;
|
||||
use ratatui::Frame;
|
||||
@@ -14,7 +13,7 @@ pub struct FileBrowserModal<'a> {
|
||||
entries: &'a [(String, bool, bool)],
|
||||
selected: usize,
|
||||
scroll_offset: usize,
|
||||
border_color: Color,
|
||||
border_color: Option<Color>,
|
||||
width: u16,
|
||||
height: u16,
|
||||
}
|
||||
@@ -27,7 +26,7 @@ impl<'a> FileBrowserModal<'a> {
|
||||
entries,
|
||||
selected: 0,
|
||||
scroll_offset: 0,
|
||||
border_color: ui::TEXT_PRIMARY,
|
||||
border_color: None,
|
||||
width: 60,
|
||||
height: 16,
|
||||
}
|
||||
@@ -44,7 +43,7 @@ impl<'a> FileBrowserModal<'a> {
|
||||
}
|
||||
|
||||
pub fn border_color(mut self, c: Color) -> Self {
|
||||
self.border_color = c;
|
||||
self.border_color = Some(c);
|
||||
self
|
||||
}
|
||||
|
||||
@@ -59,10 +58,13 @@ impl<'a> FileBrowserModal<'a> {
|
||||
}
|
||||
|
||||
pub fn render_centered(self, frame: &mut Frame, term: Rect) {
|
||||
let colors = theme::get();
|
||||
let border_color = self.border_color.unwrap_or(colors.ui.text_primary);
|
||||
|
||||
let inner = ModalFrame::new(self.title)
|
||||
.width(self.width)
|
||||
.height(self.height)
|
||||
.border_color(self.border_color)
|
||||
.border_color(border_color)
|
||||
.render_centered(frame, term);
|
||||
|
||||
let rows = Layout::vertical([Constraint::Length(1), Constraint::Min(1)]).split(inner);
|
||||
@@ -71,8 +73,8 @@ impl<'a> FileBrowserModal<'a> {
|
||||
frame.render_widget(
|
||||
Paragraph::new(Line::from(vec![
|
||||
Span::raw("> "),
|
||||
Span::styled(self.input, Style::new().fg(input::TEXT)),
|
||||
Span::styled("█", Style::new().fg(input::CURSOR)),
|
||||
Span::styled(self.input, Style::new().fg(colors.input.text)),
|
||||
Span::styled("█", Style::new().fg(colors.input.cursor)),
|
||||
])),
|
||||
rows[0],
|
||||
);
|
||||
@@ -97,13 +99,13 @@ impl<'a> FileBrowserModal<'a> {
|
||||
format!("{prefix}{name}")
|
||||
};
|
||||
let color = if is_selected {
|
||||
browser::SELECTED
|
||||
colors.browser.selected
|
||||
} else if *is_dir {
|
||||
browser::DIRECTORY
|
||||
colors.browser.directory
|
||||
} else if *is_cagire {
|
||||
browser::PROJECT_FILE
|
||||
colors.browser.project_file
|
||||
} else {
|
||||
browser::FILE
|
||||
colors.browser.file
|
||||
};
|
||||
Line::from(Span::styled(display, Style::new().fg(color)))
|
||||
})
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::theme::{hint, ui};
|
||||
use crate::theme;
|
||||
use ratatui::layout::Rect;
|
||||
use ratatui::style::{Modifier, Style};
|
||||
use ratatui::text::{Line, Span};
|
||||
@@ -51,10 +51,11 @@ impl<'a> ListSelect<'a> {
|
||||
}
|
||||
|
||||
pub fn render(self, frame: &mut Frame, area: Rect) {
|
||||
let cursor_style = Style::new().fg(hint::KEY).add_modifier(Modifier::BOLD);
|
||||
let selected_style = Style::new().fg(ui::ACCENT);
|
||||
let colors = theme::get();
|
||||
let cursor_style = Style::new().fg(colors.hint.key).add_modifier(Modifier::BOLD);
|
||||
let selected_style = Style::new().fg(colors.ui.accent);
|
||||
let normal_style = Style::default();
|
||||
let indicator_style = Style::new().fg(ui::TEXT_DIM);
|
||||
let indicator_style = Style::new().fg(colors.ui.text_dim);
|
||||
|
||||
let visible_end = (self.scroll_offset + self.visible_count).min(self.items.len());
|
||||
let has_above = self.scroll_offset > 0;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::theme::{browser, search};
|
||||
use crate::theme;
|
||||
use ratatui::layout::{Constraint, Layout, Rect};
|
||||
use ratatui::style::{Modifier, Style};
|
||||
use ratatui::text::{Line, Span};
|
||||
@@ -59,10 +59,11 @@ impl<'a> SampleBrowser<'a> {
|
||||
}
|
||||
|
||||
pub fn render(self, frame: &mut Frame, area: Rect) {
|
||||
let colors = theme::get();
|
||||
let border_style = if self.focused {
|
||||
Style::new().fg(browser::FOCUSED_BORDER)
|
||||
Style::new().fg(colors.browser.focused_border)
|
||||
} else {
|
||||
Style::new().fg(browser::UNFOCUSED_BORDER)
|
||||
Style::new().fg(colors.browser.unfocused_border)
|
||||
};
|
||||
|
||||
let block = Block::default()
|
||||
@@ -90,16 +91,16 @@ impl<'a> SampleBrowser<'a> {
|
||||
};
|
||||
|
||||
if let Some(sa) = search_area {
|
||||
self.render_search(frame, sa);
|
||||
self.render_search(frame, sa, &colors);
|
||||
}
|
||||
self.render_tree(frame, list_area);
|
||||
self.render_tree(frame, list_area, &colors);
|
||||
}
|
||||
|
||||
fn render_search(&self, frame: &mut Frame, area: Rect) {
|
||||
fn render_search(&self, frame: &mut Frame, area: Rect, colors: &theme::ThemeColors) {
|
||||
let style = if self.search_active {
|
||||
Style::new().fg(search::ACTIVE)
|
||||
Style::new().fg(colors.search.active)
|
||||
} else {
|
||||
Style::new().fg(search::INACTIVE)
|
||||
Style::new().fg(colors.search.inactive)
|
||||
};
|
||||
let cursor = if self.search_active { "_" } else { "" };
|
||||
let text = format!("/{}{}", self.search_query, cursor);
|
||||
@@ -107,7 +108,7 @@ impl<'a> SampleBrowser<'a> {
|
||||
frame.render_widget(Paragraph::new(vec![line]), area);
|
||||
}
|
||||
|
||||
fn render_tree(&self, frame: &mut Frame, area: Rect) {
|
||||
fn render_tree(&self, frame: &mut Frame, area: Rect, colors: &theme::ThemeColors) {
|
||||
let height = area.height as usize;
|
||||
if self.entries.is_empty() {
|
||||
let msg = if self.search_query.is_empty() {
|
||||
@@ -115,7 +116,7 @@ impl<'a> SampleBrowser<'a> {
|
||||
} else {
|
||||
"No matches"
|
||||
};
|
||||
let line = Line::from(Span::styled(msg, Style::new().fg(browser::EMPTY_TEXT)));
|
||||
let line = Line::from(Span::styled(msg, Style::new().fg(colors.browser.empty_text)));
|
||||
frame.render_widget(Paragraph::new(vec![line]), area);
|
||||
return;
|
||||
}
|
||||
@@ -130,23 +131,23 @@ impl<'a> SampleBrowser<'a> {
|
||||
|
||||
let (icon, icon_color) = match entry.kind {
|
||||
TreeLineKind::Root { expanded: true } | TreeLineKind::Folder { expanded: true } => {
|
||||
("\u{25BC} ", browser::FOLDER_ICON)
|
||||
("\u{25BC} ", colors.browser.folder_icon)
|
||||
}
|
||||
TreeLineKind::Root { expanded: false }
|
||||
| TreeLineKind::Folder { expanded: false } => ("\u{25B6} ", browser::FOLDER_ICON),
|
||||
TreeLineKind::File => ("\u{266A} ", browser::FILE_ICON),
|
||||
| TreeLineKind::Folder { expanded: false } => ("\u{25B6} ", colors.browser.folder_icon),
|
||||
TreeLineKind::File => ("\u{266A} ", colors.browser.file_icon),
|
||||
};
|
||||
|
||||
let label_style = if is_cursor && self.focused {
|
||||
Style::new().fg(browser::SELECTED).add_modifier(Modifier::BOLD)
|
||||
Style::new().fg(colors.browser.selected).add_modifier(Modifier::BOLD)
|
||||
} else if is_cursor {
|
||||
Style::new().fg(browser::FILE)
|
||||
Style::new().fg(colors.browser.file)
|
||||
} else {
|
||||
match entry.kind {
|
||||
TreeLineKind::Root { .. } => {
|
||||
Style::new().fg(browser::ROOT).add_modifier(Modifier::BOLD)
|
||||
Style::new().fg(colors.browser.root).add_modifier(Modifier::BOLD)
|
||||
}
|
||||
TreeLineKind::Folder { .. } => Style::new().fg(browser::DIRECTORY),
|
||||
TreeLineKind::Folder { .. } => Style::new().fg(colors.browser.directory),
|
||||
TreeLineKind::File => Style::default(),
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::theme::meter;
|
||||
use crate::theme;
|
||||
use ratatui::buffer::Buffer;
|
||||
use ratatui::layout::Rect;
|
||||
use ratatui::style::Color;
|
||||
@@ -18,7 +18,7 @@ pub enum Orientation {
|
||||
pub struct Scope<'a> {
|
||||
data: &'a [f32],
|
||||
orientation: Orientation,
|
||||
color: Color,
|
||||
color: Option<Color>,
|
||||
gain: f32,
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ impl<'a> Scope<'a> {
|
||||
Self {
|
||||
data,
|
||||
orientation: Orientation::Horizontal,
|
||||
color: meter::LOW,
|
||||
color: None,
|
||||
gain: 1.0,
|
||||
}
|
||||
}
|
||||
@@ -38,7 +38,7 @@ impl<'a> Scope<'a> {
|
||||
}
|
||||
|
||||
pub fn color(mut self, c: Color) -> Self {
|
||||
self.color = c;
|
||||
self.color = Some(c);
|
||||
self
|
||||
}
|
||||
}
|
||||
@@ -49,11 +49,13 @@ impl Widget for Scope<'_> {
|
||||
return;
|
||||
}
|
||||
|
||||
let color = self.color.unwrap_or_else(|| theme::get().meter.low);
|
||||
|
||||
match self.orientation {
|
||||
Orientation::Horizontal => {
|
||||
render_horizontal(self.data, area, buf, self.color, self.gain)
|
||||
render_horizontal(self.data, area, buf, color, self.gain)
|
||||
}
|
||||
Orientation::Vertical => render_vertical(self.data, area, buf, self.color, self.gain),
|
||||
Orientation::Vertical => render_vertical(self.data, area, buf, color, self.gain),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -64,7 +66,6 @@ fn render_horizontal(data: &[f32], area: Rect, buf: &mut Buffer, color: Color, g
|
||||
let fine_width = width * 2;
|
||||
let fine_height = height * 4;
|
||||
|
||||
// Auto-scale: find peak amplitude and normalize to fill height
|
||||
let peak = data.iter().map(|s| s.abs()).fold(0.0f32, f32::max);
|
||||
let auto_gain = if peak > 0.001 { gain / peak } else { gain };
|
||||
|
||||
@@ -121,7 +122,6 @@ fn render_vertical(data: &[f32], area: Rect, buf: &mut Buffer, color: Color, gai
|
||||
let fine_width = width * 2;
|
||||
let fine_height = height * 4;
|
||||
|
||||
// Auto-scale: find peak amplitude and normalize to fill width
|
||||
let peak = data.iter().map(|s| s.abs()).fold(0.0f32, f32::max);
|
||||
let auto_gain = if peak > 0.001 { gain / peak } else { gain };
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::theme::sparkle;
|
||||
use crate::theme;
|
||||
use rand::Rng;
|
||||
use ratatui::buffer::Buffer;
|
||||
use ratatui::layout::Rect;
|
||||
@@ -41,8 +41,9 @@ impl Sparkles {
|
||||
|
||||
impl Widget for &Sparkles {
|
||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||
let colors = theme::get().sparkle.colors;
|
||||
for sp in &self.sparkles {
|
||||
let color = sparkle::COLORS[sp.char_idx % sparkle::COLORS.len()];
|
||||
let color = colors[sp.char_idx % colors.len()];
|
||||
let intensity = (sp.life as f32 / 30.0).min(1.0);
|
||||
let r = (color.0 as f32 * intensity) as u8;
|
||||
let g = (color.1 as f32 * intensity) as u8;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::theme::meter;
|
||||
use crate::theme;
|
||||
use ratatui::buffer::Buffer;
|
||||
use ratatui::layout::Rect;
|
||||
use ratatui::style::Color;
|
||||
@@ -22,6 +22,7 @@ impl Widget for Spectrum<'_> {
|
||||
return;
|
||||
}
|
||||
|
||||
let colors = theme::get();
|
||||
let height = area.height as f32;
|
||||
let band_width = area.width as usize / 32;
|
||||
if band_width == 0 {
|
||||
@@ -40,11 +41,11 @@ impl Widget for Spectrum<'_> {
|
||||
let y = area.y + area.height - 1 - row as u16;
|
||||
let ratio = row as f32 / area.height as f32;
|
||||
let color = if ratio < 0.33 {
|
||||
Color::Rgb(meter::LOW_RGB.0, meter::LOW_RGB.1, meter::LOW_RGB.2)
|
||||
Color::Rgb(colors.meter.low_rgb.0, colors.meter.low_rgb.1, colors.meter.low_rgb.2)
|
||||
} else if ratio < 0.66 {
|
||||
Color::Rgb(meter::MID_RGB.0, meter::MID_RGB.1, meter::MID_RGB.2)
|
||||
Color::Rgb(colors.meter.mid_rgb.0, colors.meter.mid_rgb.1, colors.meter.mid_rgb.2)
|
||||
} else {
|
||||
Color::Rgb(meter::HIGH_RGB.0, meter::HIGH_RGB.1, meter::HIGH_RGB.2)
|
||||
Color::Rgb(colors.meter.high_rgb.0, colors.meter.high_rgb.1, colors.meter.high_rgb.2)
|
||||
};
|
||||
for dx in 0..band_width as u16 {
|
||||
let x = x_start + dx;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::theme::{input, ui};
|
||||
use crate::theme;
|
||||
use ratatui::layout::{Constraint, Layout, Rect};
|
||||
use ratatui::style::{Color, Style};
|
||||
use ratatui::text::{Line, Span};
|
||||
@@ -11,7 +11,7 @@ pub struct TextInputModal<'a> {
|
||||
title: &'a str,
|
||||
input: &'a str,
|
||||
hint: Option<&'a str>,
|
||||
border_color: Color,
|
||||
border_color: Option<Color>,
|
||||
width: u16,
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ impl<'a> TextInputModal<'a> {
|
||||
title,
|
||||
input,
|
||||
hint: None,
|
||||
border_color: ui::TEXT_PRIMARY,
|
||||
border_color: None,
|
||||
width: 50,
|
||||
}
|
||||
}
|
||||
@@ -32,7 +32,7 @@ impl<'a> TextInputModal<'a> {
|
||||
}
|
||||
|
||||
pub fn border_color(mut self, c: Color) -> Self {
|
||||
self.border_color = c;
|
||||
self.border_color = Some(c);
|
||||
self
|
||||
}
|
||||
|
||||
@@ -42,12 +42,14 @@ impl<'a> TextInputModal<'a> {
|
||||
}
|
||||
|
||||
pub fn render_centered(self, frame: &mut Frame, term: Rect) {
|
||||
let colors = theme::get();
|
||||
let border_color = self.border_color.unwrap_or(colors.ui.text_primary);
|
||||
let height = if self.hint.is_some() { 6 } else { 5 };
|
||||
|
||||
let inner = ModalFrame::new(self.title)
|
||||
.width(self.width)
|
||||
.height(height)
|
||||
.border_color(self.border_color)
|
||||
.border_color(border_color)
|
||||
.render_centered(frame, term);
|
||||
|
||||
if self.hint.is_some() {
|
||||
@@ -57,15 +59,15 @@ impl<'a> TextInputModal<'a> {
|
||||
frame.render_widget(
|
||||
Paragraph::new(Line::from(vec![
|
||||
Span::raw("> "),
|
||||
Span::styled(self.input, Style::new().fg(input::TEXT)),
|
||||
Span::styled("█", Style::new().fg(input::CURSOR)),
|
||||
Span::styled(self.input, Style::new().fg(colors.input.text)),
|
||||
Span::styled("█", Style::new().fg(colors.input.cursor)),
|
||||
])),
|
||||
rows[0],
|
||||
);
|
||||
|
||||
if let Some(hint) = self.hint {
|
||||
frame.render_widget(
|
||||
Paragraph::new(Span::styled(hint, Style::new().fg(input::HINT))),
|
||||
Paragraph::new(Span::styled(hint, Style::new().fg(colors.input.hint))),
|
||||
rows[1],
|
||||
);
|
||||
}
|
||||
@@ -73,8 +75,8 @@ impl<'a> TextInputModal<'a> {
|
||||
frame.render_widget(
|
||||
Paragraph::new(Line::from(vec![
|
||||
Span::raw("> "),
|
||||
Span::styled(self.input, Style::new().fg(input::TEXT)),
|
||||
Span::styled("█", Style::new().fg(input::CURSOR)),
|
||||
Span::styled(self.input, Style::new().fg(colors.input.text)),
|
||||
Span::styled("█", Style::new().fg(colors.input.cursor)),
|
||||
])),
|
||||
inner,
|
||||
);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
282
crates/ratatui/src/theme/catppuccin_latte.rs
Normal file
282
crates/ratatui/src/theme/catppuccin_latte.rs
Normal file
@@ -0,0 +1,282 @@
|
||||
use super::*;
|
||||
use ratatui::style::Color;
|
||||
|
||||
pub fn theme() -> ThemeColors {
|
||||
let crust = Color::Rgb(220, 224, 232);
|
||||
let mantle = Color::Rgb(230, 233, 239);
|
||||
let base = Color::Rgb(239, 241, 245);
|
||||
let surface0 = Color::Rgb(204, 208, 218);
|
||||
let surface1 = Color::Rgb(188, 192, 204);
|
||||
let overlay0 = Color::Rgb(156, 160, 176);
|
||||
let overlay1 = Color::Rgb(140, 143, 161);
|
||||
let subtext0 = Color::Rgb(108, 111, 133);
|
||||
let subtext1 = Color::Rgb(92, 95, 119);
|
||||
let text = Color::Rgb(76, 79, 105);
|
||||
let pink = Color::Rgb(234, 118, 203);
|
||||
let mauve = Color::Rgb(136, 57, 239);
|
||||
let red = Color::Rgb(210, 15, 57);
|
||||
let maroon = Color::Rgb(230, 69, 83);
|
||||
let peach = Color::Rgb(254, 100, 11);
|
||||
let yellow = Color::Rgb(223, 142, 29);
|
||||
let green = Color::Rgb(64, 160, 43);
|
||||
let teal = Color::Rgb(23, 146, 153);
|
||||
let sapphire = Color::Rgb(32, 159, 181);
|
||||
let lavender = Color::Rgb(114, 135, 253);
|
||||
|
||||
ThemeColors {
|
||||
ui: UiColors {
|
||||
bg: base,
|
||||
bg_rgb: (239, 241, 245),
|
||||
text_primary: text,
|
||||
text_muted: subtext0,
|
||||
text_dim: overlay1,
|
||||
border: surface1,
|
||||
header: lavender,
|
||||
unfocused: overlay0,
|
||||
accent: mauve,
|
||||
surface: surface0,
|
||||
},
|
||||
status: StatusColors {
|
||||
playing_bg: Color::Rgb(220, 240, 225),
|
||||
playing_fg: green,
|
||||
stopped_bg: Color::Rgb(245, 220, 225),
|
||||
stopped_fg: red,
|
||||
fill_on: green,
|
||||
fill_off: overlay0,
|
||||
fill_bg: surface0,
|
||||
},
|
||||
selection: SelectionColors {
|
||||
cursor_bg: mauve,
|
||||
cursor_fg: base,
|
||||
selected_bg: Color::Rgb(200, 200, 230),
|
||||
selected_fg: lavender,
|
||||
in_range_bg: Color::Rgb(210, 210, 235),
|
||||
in_range_fg: subtext1,
|
||||
cursor: mauve,
|
||||
selected: Color::Rgb(200, 200, 230),
|
||||
in_range: Color::Rgb(210, 210, 235),
|
||||
},
|
||||
tile: TileColors {
|
||||
playing_active_bg: Color::Rgb(250, 220, 210),
|
||||
playing_active_fg: peach,
|
||||
playing_inactive_bg: Color::Rgb(250, 235, 200),
|
||||
playing_inactive_fg: yellow,
|
||||
active_bg: Color::Rgb(200, 235, 235),
|
||||
active_fg: teal,
|
||||
inactive_bg: surface0,
|
||||
inactive_fg: subtext0,
|
||||
active_selected_bg: Color::Rgb(215, 210, 240),
|
||||
active_in_range_bg: Color::Rgb(210, 215, 230),
|
||||
link_bright: [
|
||||
(136, 57, 239),
|
||||
(234, 118, 203),
|
||||
(254, 100, 11),
|
||||
(4, 165, 229),
|
||||
(64, 160, 43),
|
||||
],
|
||||
link_dim: [
|
||||
(210, 200, 240),
|
||||
(240, 210, 230),
|
||||
(250, 220, 200),
|
||||
(200, 230, 240),
|
||||
(210, 235, 210),
|
||||
],
|
||||
},
|
||||
header: HeaderColors {
|
||||
tempo_bg: Color::Rgb(220, 210, 240),
|
||||
tempo_fg: mauve,
|
||||
bank_bg: Color::Rgb(200, 230, 235),
|
||||
bank_fg: sapphire,
|
||||
pattern_bg: Color::Rgb(200, 230, 225),
|
||||
pattern_fg: teal,
|
||||
stats_bg: surface0,
|
||||
stats_fg: subtext0,
|
||||
},
|
||||
modal: ModalColors {
|
||||
border: lavender,
|
||||
border_accent: mauve,
|
||||
border_warn: peach,
|
||||
border_dim: overlay1,
|
||||
confirm: peach,
|
||||
rename: mauve,
|
||||
input: sapphire,
|
||||
editor: lavender,
|
||||
preview: overlay1,
|
||||
},
|
||||
flash: FlashColors {
|
||||
error_bg: Color::Rgb(250, 215, 220),
|
||||
error_fg: red,
|
||||
success_bg: Color::Rgb(210, 240, 215),
|
||||
success_fg: green,
|
||||
info_bg: surface0,
|
||||
info_fg: text,
|
||||
event_rgb: (225, 215, 240),
|
||||
},
|
||||
list: ListColors {
|
||||
playing_bg: Color::Rgb(210, 235, 220),
|
||||
playing_fg: green,
|
||||
staged_play_bg: Color::Rgb(225, 215, 245),
|
||||
staged_play_fg: mauve,
|
||||
staged_stop_bg: Color::Rgb(245, 215, 225),
|
||||
staged_stop_fg: maroon,
|
||||
edit_bg: Color::Rgb(210, 235, 235),
|
||||
edit_fg: teal,
|
||||
hover_bg: surface1,
|
||||
hover_fg: text,
|
||||
},
|
||||
link_status: LinkStatusColors {
|
||||
disabled: red,
|
||||
connected: green,
|
||||
listening: yellow,
|
||||
},
|
||||
syntax: SyntaxColors {
|
||||
gap_bg: mantle,
|
||||
executed_bg: Color::Rgb(225, 220, 240),
|
||||
selected_bg: Color::Rgb(250, 235, 210),
|
||||
emit: (text, Color::Rgb(250, 220, 215)),
|
||||
number: (peach, Color::Rgb(252, 235, 220)),
|
||||
string: (green, Color::Rgb(215, 240, 215)),
|
||||
comment: (overlay1, crust),
|
||||
keyword: (mauve, Color::Rgb(230, 220, 245)),
|
||||
stack_op: (sapphire, Color::Rgb(215, 230, 240)),
|
||||
operator: (yellow, Color::Rgb(245, 235, 210)),
|
||||
sound: (teal, Color::Rgb(210, 240, 240)),
|
||||
param: (lavender, Color::Rgb(220, 225, 245)),
|
||||
context: (peach, Color::Rgb(252, 235, 220)),
|
||||
note: (green, Color::Rgb(215, 240, 215)),
|
||||
interval: (Color::Rgb(50, 140, 30), Color::Rgb(215, 240, 210)),
|
||||
variable: (pink, Color::Rgb(245, 220, 240)),
|
||||
vary: (yellow, Color::Rgb(245, 235, 210)),
|
||||
generator: (teal, Color::Rgb(210, 240, 235)),
|
||||
default: (subtext0, mantle),
|
||||
},
|
||||
table: TableColors {
|
||||
row_even: mantle,
|
||||
row_odd: base,
|
||||
},
|
||||
values: ValuesColors {
|
||||
tempo: peach,
|
||||
value: subtext0,
|
||||
},
|
||||
hint: HintColors {
|
||||
key: peach,
|
||||
text: overlay1,
|
||||
},
|
||||
view_badge: ViewBadgeColors { bg: text, fg: base },
|
||||
nav: NavColors {
|
||||
selected_bg: Color::Rgb(215, 205, 245),
|
||||
selected_fg: text,
|
||||
unselected_bg: surface0,
|
||||
unselected_fg: overlay1,
|
||||
},
|
||||
editor_widget: EditorWidgetColors {
|
||||
cursor_bg: text,
|
||||
cursor_fg: base,
|
||||
selection_bg: Color::Rgb(200, 210, 240),
|
||||
completion_bg: surface0,
|
||||
completion_fg: text,
|
||||
completion_selected: peach,
|
||||
completion_example: teal,
|
||||
},
|
||||
browser: BrowserColors {
|
||||
directory: sapphire,
|
||||
project_file: mauve,
|
||||
selected: peach,
|
||||
file: text,
|
||||
focused_border: peach,
|
||||
unfocused_border: overlay0,
|
||||
root: text,
|
||||
file_icon: overlay1,
|
||||
folder_icon: sapphire,
|
||||
empty_text: overlay1,
|
||||
},
|
||||
input: InputColors {
|
||||
text: sapphire,
|
||||
cursor: text,
|
||||
hint: overlay1,
|
||||
},
|
||||
search: SearchColors {
|
||||
active: peach,
|
||||
inactive: overlay0,
|
||||
match_bg: yellow,
|
||||
match_fg: base,
|
||||
},
|
||||
markdown: MarkdownColors {
|
||||
h1: sapphire,
|
||||
h2: peach,
|
||||
h3: mauve,
|
||||
code: green,
|
||||
code_border: Color::Rgb(190, 195, 205),
|
||||
link: teal,
|
||||
link_url: Color::Rgb(150, 150, 150),
|
||||
quote: overlay1,
|
||||
text,
|
||||
list: text,
|
||||
},
|
||||
engine: EngineColors {
|
||||
header: Color::Rgb(30, 120, 150),
|
||||
header_focused: yellow,
|
||||
divider: Color::Rgb(180, 185, 195),
|
||||
scroll_indicator: Color::Rgb(160, 165, 175),
|
||||
label: Color::Rgb(100, 105, 120),
|
||||
label_focused: Color::Rgb(70, 75, 90),
|
||||
label_dim: Color::Rgb(120, 125, 140),
|
||||
value: Color::Rgb(60, 65, 80),
|
||||
focused: yellow,
|
||||
normal: text,
|
||||
dim: Color::Rgb(160, 165, 175),
|
||||
path: Color::Rgb(100, 105, 120),
|
||||
border_magenta: mauve,
|
||||
border_green: green,
|
||||
border_cyan: sapphire,
|
||||
separator: Color::Rgb(180, 185, 200),
|
||||
hint_active: Color::Rgb(180, 140, 40),
|
||||
hint_inactive: Color::Rgb(190, 195, 205),
|
||||
},
|
||||
dict: DictColors {
|
||||
word_name: green,
|
||||
word_bg: Color::Rgb(210, 225, 235),
|
||||
alias: overlay1,
|
||||
stack_sig: mauve,
|
||||
description: text,
|
||||
example: Color::Rgb(100, 105, 115),
|
||||
category_focused: yellow,
|
||||
category_selected: sapphire,
|
||||
category_normal: text,
|
||||
category_dimmed: Color::Rgb(160, 165, 175),
|
||||
border_focused: yellow,
|
||||
border_normal: Color::Rgb(180, 185, 195),
|
||||
header_desc: Color::Rgb(90, 95, 110),
|
||||
},
|
||||
title: TitleColors {
|
||||
big_title: mauve,
|
||||
author: lavender,
|
||||
link: teal,
|
||||
license: peach,
|
||||
prompt: Color::Rgb(90, 100, 115),
|
||||
subtitle: text,
|
||||
},
|
||||
meter: MeterColors {
|
||||
low: green,
|
||||
mid: yellow,
|
||||
high: red,
|
||||
low_rgb: (50, 150, 40),
|
||||
mid_rgb: (200, 140, 30),
|
||||
high_rgb: (200, 40, 50),
|
||||
},
|
||||
sparkle: SparkleColors {
|
||||
colors: [
|
||||
(114, 135, 253),
|
||||
(254, 100, 11),
|
||||
(64, 160, 43),
|
||||
(234, 118, 203),
|
||||
(136, 57, 239),
|
||||
],
|
||||
},
|
||||
confirm: ConfirmColors {
|
||||
border: peach,
|
||||
button_selected_bg: peach,
|
||||
button_selected_fg: base,
|
||||
},
|
||||
}
|
||||
}
|
||||
285
crates/ratatui/src/theme/catppuccin_mocha.rs
Normal file
285
crates/ratatui/src/theme/catppuccin_mocha.rs
Normal file
@@ -0,0 +1,285 @@
|
||||
use super::*;
|
||||
use ratatui::style::Color;
|
||||
|
||||
pub fn theme() -> ThemeColors {
|
||||
let crust = Color::Rgb(17, 17, 27);
|
||||
let mantle = Color::Rgb(24, 24, 37);
|
||||
let base = Color::Rgb(30, 30, 46);
|
||||
let surface0 = Color::Rgb(49, 50, 68);
|
||||
let surface1 = Color::Rgb(69, 71, 90);
|
||||
let overlay0 = Color::Rgb(108, 112, 134);
|
||||
let overlay1 = Color::Rgb(127, 132, 156);
|
||||
let subtext0 = Color::Rgb(166, 173, 200);
|
||||
let subtext1 = Color::Rgb(186, 194, 222);
|
||||
let text = Color::Rgb(205, 214, 244);
|
||||
let pink = Color::Rgb(245, 194, 231);
|
||||
let mauve = Color::Rgb(203, 166, 247);
|
||||
let red = Color::Rgb(243, 139, 168);
|
||||
let maroon = Color::Rgb(235, 160, 172);
|
||||
let peach = Color::Rgb(250, 179, 135);
|
||||
let yellow = Color::Rgb(249, 226, 175);
|
||||
let green = Color::Rgb(166, 227, 161);
|
||||
let teal = Color::Rgb(148, 226, 213);
|
||||
let sapphire = Color::Rgb(116, 199, 236);
|
||||
let lavender = Color::Rgb(180, 190, 254);
|
||||
|
||||
ThemeColors {
|
||||
ui: UiColors {
|
||||
bg: base,
|
||||
bg_rgb: (30, 30, 46),
|
||||
text_primary: text,
|
||||
text_muted: subtext0,
|
||||
text_dim: overlay1,
|
||||
border: surface1,
|
||||
header: lavender,
|
||||
unfocused: overlay0,
|
||||
accent: mauve,
|
||||
surface: surface0,
|
||||
},
|
||||
status: StatusColors {
|
||||
playing_bg: Color::Rgb(30, 50, 40),
|
||||
playing_fg: green,
|
||||
stopped_bg: Color::Rgb(50, 30, 40),
|
||||
stopped_fg: red,
|
||||
fill_on: green,
|
||||
fill_off: overlay0,
|
||||
fill_bg: surface0,
|
||||
},
|
||||
selection: SelectionColors {
|
||||
cursor_bg: mauve,
|
||||
cursor_fg: crust,
|
||||
selected_bg: Color::Rgb(60, 60, 90),
|
||||
selected_fg: lavender,
|
||||
in_range_bg: Color::Rgb(50, 50, 75),
|
||||
in_range_fg: subtext1,
|
||||
cursor: mauve,
|
||||
selected: Color::Rgb(60, 60, 90),
|
||||
in_range: Color::Rgb(50, 50, 75),
|
||||
},
|
||||
tile: TileColors {
|
||||
playing_active_bg: Color::Rgb(80, 50, 60),
|
||||
playing_active_fg: peach,
|
||||
playing_inactive_bg: Color::Rgb(70, 55, 45),
|
||||
playing_inactive_fg: yellow,
|
||||
active_bg: Color::Rgb(40, 55, 55),
|
||||
active_fg: teal,
|
||||
inactive_bg: surface0,
|
||||
inactive_fg: subtext0,
|
||||
active_selected_bg: Color::Rgb(70, 60, 80),
|
||||
active_in_range_bg: Color::Rgb(55, 55, 70),
|
||||
link_bright: [
|
||||
(203, 166, 247),
|
||||
(245, 194, 231),
|
||||
(250, 179, 135),
|
||||
(137, 220, 235),
|
||||
(166, 227, 161),
|
||||
],
|
||||
link_dim: [
|
||||
(70, 55, 85),
|
||||
(85, 65, 80),
|
||||
(85, 60, 45),
|
||||
(45, 75, 80),
|
||||
(55, 80, 55),
|
||||
],
|
||||
},
|
||||
header: HeaderColors {
|
||||
tempo_bg: Color::Rgb(50, 40, 60),
|
||||
tempo_fg: mauve,
|
||||
bank_bg: Color::Rgb(35, 50, 55),
|
||||
bank_fg: sapphire,
|
||||
pattern_bg: Color::Rgb(40, 50, 50),
|
||||
pattern_fg: teal,
|
||||
stats_bg: surface0,
|
||||
stats_fg: subtext0,
|
||||
},
|
||||
modal: ModalColors {
|
||||
border: lavender,
|
||||
border_accent: mauve,
|
||||
border_warn: peach,
|
||||
border_dim: overlay1,
|
||||
confirm: peach,
|
||||
rename: mauve,
|
||||
input: sapphire,
|
||||
editor: lavender,
|
||||
preview: overlay1,
|
||||
},
|
||||
flash: FlashColors {
|
||||
error_bg: Color::Rgb(50, 30, 40),
|
||||
error_fg: red,
|
||||
success_bg: Color::Rgb(30, 50, 40),
|
||||
success_fg: green,
|
||||
info_bg: surface0,
|
||||
info_fg: text,
|
||||
event_rgb: (55, 45, 70),
|
||||
},
|
||||
list: ListColors {
|
||||
playing_bg: Color::Rgb(35, 55, 45),
|
||||
playing_fg: green,
|
||||
staged_play_bg: Color::Rgb(55, 45, 65),
|
||||
staged_play_fg: mauve,
|
||||
staged_stop_bg: Color::Rgb(60, 40, 50),
|
||||
staged_stop_fg: maroon,
|
||||
edit_bg: Color::Rgb(40, 55, 55),
|
||||
edit_fg: teal,
|
||||
hover_bg: surface1,
|
||||
hover_fg: text,
|
||||
},
|
||||
link_status: LinkStatusColors {
|
||||
disabled: red,
|
||||
connected: green,
|
||||
listening: yellow,
|
||||
},
|
||||
syntax: SyntaxColors {
|
||||
gap_bg: mantle,
|
||||
executed_bg: Color::Rgb(45, 40, 55),
|
||||
selected_bg: Color::Rgb(70, 55, 40),
|
||||
emit: (text, Color::Rgb(80, 50, 60)),
|
||||
number: (peach, Color::Rgb(55, 45, 35)),
|
||||
string: (green, Color::Rgb(35, 50, 40)),
|
||||
comment: (overlay1, crust),
|
||||
keyword: (mauve, Color::Rgb(50, 40, 60)),
|
||||
stack_op: (sapphire, Color::Rgb(35, 45, 55)),
|
||||
operator: (yellow, Color::Rgb(55, 50, 35)),
|
||||
sound: (teal, Color::Rgb(35, 55, 55)),
|
||||
param: (lavender, Color::Rgb(45, 45, 60)),
|
||||
context: (peach, Color::Rgb(55, 45, 35)),
|
||||
note: (green, Color::Rgb(35, 50, 40)),
|
||||
interval: (Color::Rgb(180, 230, 150), Color::Rgb(40, 55, 35)),
|
||||
variable: (pink, Color::Rgb(55, 40, 55)),
|
||||
vary: (yellow, Color::Rgb(55, 50, 35)),
|
||||
generator: (teal, Color::Rgb(35, 55, 50)),
|
||||
default: (subtext0, mantle),
|
||||
},
|
||||
table: TableColors {
|
||||
row_even: mantle,
|
||||
row_odd: base,
|
||||
},
|
||||
values: ValuesColors {
|
||||
tempo: peach,
|
||||
value: subtext0,
|
||||
},
|
||||
hint: HintColors {
|
||||
key: peach,
|
||||
text: overlay1,
|
||||
},
|
||||
view_badge: ViewBadgeColors {
|
||||
bg: text,
|
||||
fg: crust,
|
||||
},
|
||||
nav: NavColors {
|
||||
selected_bg: Color::Rgb(60, 50, 75),
|
||||
selected_fg: text,
|
||||
unselected_bg: surface0,
|
||||
unselected_fg: overlay1,
|
||||
},
|
||||
editor_widget: EditorWidgetColors {
|
||||
cursor_bg: text,
|
||||
cursor_fg: crust,
|
||||
selection_bg: Color::Rgb(50, 60, 90),
|
||||
completion_bg: surface0,
|
||||
completion_fg: text,
|
||||
completion_selected: peach,
|
||||
completion_example: teal,
|
||||
},
|
||||
browser: BrowserColors {
|
||||
directory: sapphire,
|
||||
project_file: mauve,
|
||||
selected: peach,
|
||||
file: text,
|
||||
focused_border: peach,
|
||||
unfocused_border: overlay0,
|
||||
root: text,
|
||||
file_icon: overlay1,
|
||||
folder_icon: sapphire,
|
||||
empty_text: overlay1,
|
||||
},
|
||||
input: InputColors {
|
||||
text: sapphire,
|
||||
cursor: text,
|
||||
hint: overlay1,
|
||||
},
|
||||
search: SearchColors {
|
||||
active: peach,
|
||||
inactive: overlay0,
|
||||
match_bg: yellow,
|
||||
match_fg: crust,
|
||||
},
|
||||
markdown: MarkdownColors {
|
||||
h1: sapphire,
|
||||
h2: peach,
|
||||
h3: mauve,
|
||||
code: green,
|
||||
code_border: Color::Rgb(60, 60, 70),
|
||||
link: teal,
|
||||
link_url: Color::Rgb(100, 100, 100),
|
||||
quote: overlay1,
|
||||
text,
|
||||
list: text,
|
||||
},
|
||||
engine: EngineColors {
|
||||
header: Color::Rgb(100, 160, 180),
|
||||
header_focused: yellow,
|
||||
divider: Color::Rgb(60, 65, 70),
|
||||
scroll_indicator: Color::Rgb(80, 85, 95),
|
||||
label: Color::Rgb(120, 125, 135),
|
||||
label_focused: Color::Rgb(150, 155, 165),
|
||||
label_dim: Color::Rgb(100, 105, 115),
|
||||
value: Color::Rgb(180, 180, 190),
|
||||
focused: yellow,
|
||||
normal: text,
|
||||
dim: Color::Rgb(80, 85, 95),
|
||||
path: Color::Rgb(120, 125, 135),
|
||||
border_magenta: mauve,
|
||||
border_green: green,
|
||||
border_cyan: sapphire,
|
||||
separator: Color::Rgb(60, 65, 75),
|
||||
hint_active: Color::Rgb(180, 180, 100),
|
||||
hint_inactive: Color::Rgb(60, 60, 70),
|
||||
},
|
||||
dict: DictColors {
|
||||
word_name: green,
|
||||
word_bg: Color::Rgb(40, 50, 60),
|
||||
alias: overlay1,
|
||||
stack_sig: mauve,
|
||||
description: text,
|
||||
example: Color::Rgb(120, 130, 140),
|
||||
category_focused: yellow,
|
||||
category_selected: sapphire,
|
||||
category_normal: text,
|
||||
category_dimmed: Color::Rgb(80, 80, 90),
|
||||
border_focused: yellow,
|
||||
border_normal: Color::Rgb(60, 60, 70),
|
||||
header_desc: Color::Rgb(140, 145, 155),
|
||||
},
|
||||
title: TitleColors {
|
||||
big_title: mauve,
|
||||
author: lavender,
|
||||
link: teal,
|
||||
license: peach,
|
||||
prompt: Color::Rgb(140, 160, 170),
|
||||
subtitle: text,
|
||||
},
|
||||
meter: MeterColors {
|
||||
low: green,
|
||||
mid: yellow,
|
||||
high: red,
|
||||
low_rgb: (40, 180, 80),
|
||||
mid_rgb: (220, 180, 40),
|
||||
high_rgb: (220, 60, 40),
|
||||
},
|
||||
sparkle: SparkleColors {
|
||||
colors: [
|
||||
(200, 220, 255),
|
||||
(250, 179, 135),
|
||||
(166, 227, 161),
|
||||
(245, 194, 231),
|
||||
(203, 166, 247),
|
||||
],
|
||||
},
|
||||
confirm: ConfirmColors {
|
||||
border: peach,
|
||||
button_selected_bg: peach,
|
||||
button_selected_fg: crust,
|
||||
},
|
||||
}
|
||||
}
|
||||
279
crates/ratatui/src/theme/dracula.rs
Normal file
279
crates/ratatui/src/theme/dracula.rs
Normal file
@@ -0,0 +1,279 @@
|
||||
use super::*;
|
||||
use ratatui::style::Color;
|
||||
|
||||
pub fn theme() -> ThemeColors {
|
||||
let background = Color::Rgb(40, 42, 54);
|
||||
let current_line = Color::Rgb(68, 71, 90);
|
||||
let foreground = Color::Rgb(248, 248, 242);
|
||||
let comment = Color::Rgb(98, 114, 164);
|
||||
let cyan = Color::Rgb(139, 233, 253);
|
||||
let green = Color::Rgb(80, 250, 123);
|
||||
let orange = Color::Rgb(255, 184, 108);
|
||||
let pink = Color::Rgb(255, 121, 198);
|
||||
let purple = Color::Rgb(189, 147, 249);
|
||||
let red = Color::Rgb(255, 85, 85);
|
||||
let yellow = Color::Rgb(241, 250, 140);
|
||||
|
||||
let darker_bg = Color::Rgb(33, 34, 44);
|
||||
let lighter_bg = Color::Rgb(55, 57, 70);
|
||||
|
||||
ThemeColors {
|
||||
ui: UiColors {
|
||||
bg: background,
|
||||
bg_rgb: (40, 42, 54),
|
||||
text_primary: foreground,
|
||||
text_muted: comment,
|
||||
text_dim: Color::Rgb(80, 85, 110),
|
||||
border: current_line,
|
||||
header: purple,
|
||||
unfocused: comment,
|
||||
accent: purple,
|
||||
surface: current_line,
|
||||
},
|
||||
status: StatusColors {
|
||||
playing_bg: Color::Rgb(40, 60, 50),
|
||||
playing_fg: green,
|
||||
stopped_bg: Color::Rgb(65, 45, 50),
|
||||
stopped_fg: red,
|
||||
fill_on: green,
|
||||
fill_off: comment,
|
||||
fill_bg: current_line,
|
||||
},
|
||||
selection: SelectionColors {
|
||||
cursor_bg: purple,
|
||||
cursor_fg: background,
|
||||
selected_bg: Color::Rgb(80, 75, 110),
|
||||
selected_fg: purple,
|
||||
in_range_bg: Color::Rgb(65, 65, 90),
|
||||
in_range_fg: foreground,
|
||||
cursor: purple,
|
||||
selected: Color::Rgb(80, 75, 110),
|
||||
in_range: Color::Rgb(65, 65, 90),
|
||||
},
|
||||
tile: TileColors {
|
||||
playing_active_bg: Color::Rgb(85, 60, 65),
|
||||
playing_active_fg: orange,
|
||||
playing_inactive_bg: Color::Rgb(80, 75, 55),
|
||||
playing_inactive_fg: yellow,
|
||||
active_bg: Color::Rgb(50, 70, 70),
|
||||
active_fg: cyan,
|
||||
inactive_bg: current_line,
|
||||
inactive_fg: comment,
|
||||
active_selected_bg: Color::Rgb(80, 70, 95),
|
||||
active_in_range_bg: Color::Rgb(65, 65, 85),
|
||||
link_bright: [
|
||||
(189, 147, 249),
|
||||
(255, 121, 198),
|
||||
(255, 184, 108),
|
||||
(139, 233, 253),
|
||||
(80, 250, 123),
|
||||
],
|
||||
link_dim: [
|
||||
(75, 60, 95),
|
||||
(95, 55, 80),
|
||||
(95, 70, 50),
|
||||
(55, 90, 95),
|
||||
(40, 95, 55),
|
||||
],
|
||||
},
|
||||
header: HeaderColors {
|
||||
tempo_bg: Color::Rgb(65, 50, 75),
|
||||
tempo_fg: purple,
|
||||
bank_bg: Color::Rgb(45, 65, 70),
|
||||
bank_fg: cyan,
|
||||
pattern_bg: Color::Rgb(40, 70, 60),
|
||||
pattern_fg: green,
|
||||
stats_bg: current_line,
|
||||
stats_fg: comment,
|
||||
},
|
||||
modal: ModalColors {
|
||||
border: purple,
|
||||
border_accent: pink,
|
||||
border_warn: orange,
|
||||
border_dim: comment,
|
||||
confirm: orange,
|
||||
rename: purple,
|
||||
input: cyan,
|
||||
editor: purple,
|
||||
preview: comment,
|
||||
},
|
||||
flash: FlashColors {
|
||||
error_bg: Color::Rgb(70, 45, 50),
|
||||
error_fg: red,
|
||||
success_bg: Color::Rgb(40, 65, 50),
|
||||
success_fg: green,
|
||||
info_bg: current_line,
|
||||
info_fg: foreground,
|
||||
event_rgb: (70, 55, 85),
|
||||
},
|
||||
list: ListColors {
|
||||
playing_bg: Color::Rgb(40, 65, 50),
|
||||
playing_fg: green,
|
||||
staged_play_bg: Color::Rgb(70, 55, 85),
|
||||
staged_play_fg: purple,
|
||||
staged_stop_bg: Color::Rgb(80, 50, 60),
|
||||
staged_stop_fg: red,
|
||||
edit_bg: Color::Rgb(45, 70, 70),
|
||||
edit_fg: cyan,
|
||||
hover_bg: lighter_bg,
|
||||
hover_fg: foreground,
|
||||
},
|
||||
link_status: LinkStatusColors {
|
||||
disabled: red,
|
||||
connected: green,
|
||||
listening: yellow,
|
||||
},
|
||||
syntax: SyntaxColors {
|
||||
gap_bg: darker_bg,
|
||||
executed_bg: Color::Rgb(55, 50, 70),
|
||||
selected_bg: Color::Rgb(85, 70, 50),
|
||||
emit: (foreground, Color::Rgb(85, 55, 65)),
|
||||
number: (orange, Color::Rgb(75, 55, 45)),
|
||||
string: (yellow, Color::Rgb(70, 70, 45)),
|
||||
comment: (comment, darker_bg),
|
||||
keyword: (pink, Color::Rgb(80, 50, 70)),
|
||||
stack_op: (cyan, Color::Rgb(45, 65, 75)),
|
||||
operator: (green, Color::Rgb(40, 70, 50)),
|
||||
sound: (cyan, Color::Rgb(45, 70, 70)),
|
||||
param: (purple, Color::Rgb(60, 50, 75)),
|
||||
context: (orange, Color::Rgb(75, 55, 45)),
|
||||
note: (green, Color::Rgb(40, 70, 50)),
|
||||
interval: (Color::Rgb(120, 255, 150), Color::Rgb(40, 75, 50)),
|
||||
variable: (pink, Color::Rgb(80, 50, 65)),
|
||||
vary: (yellow, Color::Rgb(70, 70, 45)),
|
||||
generator: (cyan, Color::Rgb(45, 70, 65)),
|
||||
default: (comment, darker_bg),
|
||||
},
|
||||
table: TableColors {
|
||||
row_even: darker_bg,
|
||||
row_odd: background,
|
||||
},
|
||||
values: ValuesColors {
|
||||
tempo: orange,
|
||||
value: comment,
|
||||
},
|
||||
hint: HintColors {
|
||||
key: orange,
|
||||
text: comment,
|
||||
},
|
||||
view_badge: ViewBadgeColors {
|
||||
bg: foreground,
|
||||
fg: background,
|
||||
},
|
||||
nav: NavColors {
|
||||
selected_bg: Color::Rgb(75, 65, 100),
|
||||
selected_fg: foreground,
|
||||
unselected_bg: current_line,
|
||||
unselected_fg: comment,
|
||||
},
|
||||
editor_widget: EditorWidgetColors {
|
||||
cursor_bg: foreground,
|
||||
cursor_fg: background,
|
||||
selection_bg: Color::Rgb(70, 75, 105),
|
||||
completion_bg: current_line,
|
||||
completion_fg: foreground,
|
||||
completion_selected: orange,
|
||||
completion_example: cyan,
|
||||
},
|
||||
browser: BrowserColors {
|
||||
directory: cyan,
|
||||
project_file: purple,
|
||||
selected: orange,
|
||||
file: foreground,
|
||||
focused_border: orange,
|
||||
unfocused_border: comment,
|
||||
root: foreground,
|
||||
file_icon: comment,
|
||||
folder_icon: cyan,
|
||||
empty_text: comment,
|
||||
},
|
||||
input: InputColors {
|
||||
text: cyan,
|
||||
cursor: foreground,
|
||||
hint: comment,
|
||||
},
|
||||
search: SearchColors {
|
||||
active: orange,
|
||||
inactive: comment,
|
||||
match_bg: yellow,
|
||||
match_fg: background,
|
||||
},
|
||||
markdown: MarkdownColors {
|
||||
h1: cyan,
|
||||
h2: orange,
|
||||
h3: purple,
|
||||
code: green,
|
||||
code_border: Color::Rgb(85, 90, 110),
|
||||
link: pink,
|
||||
link_url: Color::Rgb(120, 130, 150),
|
||||
quote: comment,
|
||||
text: foreground,
|
||||
list: foreground,
|
||||
},
|
||||
engine: EngineColors {
|
||||
header: cyan,
|
||||
header_focused: yellow,
|
||||
divider: Color::Rgb(80, 85, 105),
|
||||
scroll_indicator: Color::Rgb(95, 100, 120),
|
||||
label: Color::Rgb(140, 145, 165),
|
||||
label_focused: Color::Rgb(170, 175, 195),
|
||||
label_dim: Color::Rgb(110, 115, 135),
|
||||
value: Color::Rgb(200, 205, 220),
|
||||
focused: yellow,
|
||||
normal: foreground,
|
||||
dim: Color::Rgb(95, 100, 120),
|
||||
path: Color::Rgb(140, 145, 165),
|
||||
border_magenta: pink,
|
||||
border_green: green,
|
||||
border_cyan: cyan,
|
||||
separator: Color::Rgb(80, 85, 105),
|
||||
hint_active: Color::Rgb(220, 200, 100),
|
||||
hint_inactive: Color::Rgb(80, 85, 105),
|
||||
},
|
||||
dict: DictColors {
|
||||
word_name: green,
|
||||
word_bg: Color::Rgb(55, 65, 80),
|
||||
alias: comment,
|
||||
stack_sig: purple,
|
||||
description: foreground,
|
||||
example: Color::Rgb(140, 145, 165),
|
||||
category_focused: yellow,
|
||||
category_selected: cyan,
|
||||
category_normal: foreground,
|
||||
category_dimmed: Color::Rgb(95, 100, 120),
|
||||
border_focused: yellow,
|
||||
border_normal: Color::Rgb(80, 85, 105),
|
||||
header_desc: Color::Rgb(160, 165, 185),
|
||||
},
|
||||
title: TitleColors {
|
||||
big_title: purple,
|
||||
author: pink,
|
||||
link: cyan,
|
||||
license: orange,
|
||||
prompt: Color::Rgb(160, 165, 185),
|
||||
subtitle: foreground,
|
||||
},
|
||||
meter: MeterColors {
|
||||
low: green,
|
||||
mid: yellow,
|
||||
high: red,
|
||||
low_rgb: (70, 230, 110),
|
||||
mid_rgb: (230, 240, 130),
|
||||
high_rgb: (240, 80, 80),
|
||||
},
|
||||
sparkle: SparkleColors {
|
||||
colors: [
|
||||
(189, 147, 249),
|
||||
(255, 184, 108),
|
||||
(80, 250, 123),
|
||||
(255, 121, 198),
|
||||
(139, 233, 253),
|
||||
],
|
||||
},
|
||||
confirm: ConfirmColors {
|
||||
border: orange,
|
||||
button_selected_bg: orange,
|
||||
button_selected_fg: background,
|
||||
},
|
||||
}
|
||||
}
|
||||
278
crates/ratatui/src/theme/gruvbox_dark.rs
Normal file
278
crates/ratatui/src/theme/gruvbox_dark.rs
Normal file
@@ -0,0 +1,278 @@
|
||||
use super::*;
|
||||
use ratatui::style::Color;
|
||||
|
||||
pub fn theme() -> ThemeColors {
|
||||
let bg0 = Color::Rgb(40, 40, 40);
|
||||
let bg1 = Color::Rgb(60, 56, 54);
|
||||
let bg2 = Color::Rgb(80, 73, 69);
|
||||
let fg = Color::Rgb(235, 219, 178);
|
||||
let fg2 = Color::Rgb(213, 196, 161);
|
||||
let fg3 = Color::Rgb(189, 174, 147);
|
||||
let fg4 = Color::Rgb(168, 153, 132);
|
||||
let red = Color::Rgb(251, 73, 52);
|
||||
let green = Color::Rgb(184, 187, 38);
|
||||
let yellow = Color::Rgb(250, 189, 47);
|
||||
let blue = Color::Rgb(131, 165, 152);
|
||||
let purple = Color::Rgb(211, 134, 155);
|
||||
let aqua = Color::Rgb(142, 192, 124);
|
||||
let orange = Color::Rgb(254, 128, 25);
|
||||
|
||||
let darker_bg = Color::Rgb(29, 32, 33);
|
||||
|
||||
ThemeColors {
|
||||
ui: UiColors {
|
||||
bg: bg0,
|
||||
bg_rgb: (40, 40, 40),
|
||||
text_primary: fg,
|
||||
text_muted: fg3,
|
||||
text_dim: fg4,
|
||||
border: bg2,
|
||||
header: yellow,
|
||||
unfocused: fg4,
|
||||
accent: orange,
|
||||
surface: bg1,
|
||||
},
|
||||
status: StatusColors {
|
||||
playing_bg: Color::Rgb(50, 60, 45),
|
||||
playing_fg: green,
|
||||
stopped_bg: Color::Rgb(65, 45, 45),
|
||||
stopped_fg: red,
|
||||
fill_on: green,
|
||||
fill_off: fg4,
|
||||
fill_bg: bg1,
|
||||
},
|
||||
selection: SelectionColors {
|
||||
cursor_bg: orange,
|
||||
cursor_fg: bg0,
|
||||
selected_bg: Color::Rgb(80, 70, 55),
|
||||
selected_fg: yellow,
|
||||
in_range_bg: Color::Rgb(65, 60, 50),
|
||||
in_range_fg: fg2,
|
||||
cursor: orange,
|
||||
selected: Color::Rgb(80, 70, 55),
|
||||
in_range: Color::Rgb(65, 60, 50),
|
||||
},
|
||||
tile: TileColors {
|
||||
playing_active_bg: Color::Rgb(90, 65, 50),
|
||||
playing_active_fg: orange,
|
||||
playing_inactive_bg: Color::Rgb(80, 75, 45),
|
||||
playing_inactive_fg: yellow,
|
||||
active_bg: Color::Rgb(50, 65, 55),
|
||||
active_fg: aqua,
|
||||
inactive_bg: bg1,
|
||||
inactive_fg: fg3,
|
||||
active_selected_bg: Color::Rgb(85, 70, 60),
|
||||
active_in_range_bg: Color::Rgb(70, 65, 55),
|
||||
link_bright: [
|
||||
(254, 128, 25),
|
||||
(211, 134, 155),
|
||||
(250, 189, 47),
|
||||
(131, 165, 152),
|
||||
(184, 187, 38),
|
||||
],
|
||||
link_dim: [
|
||||
(85, 55, 35),
|
||||
(75, 55, 65),
|
||||
(80, 70, 40),
|
||||
(50, 60, 60),
|
||||
(60, 65, 35),
|
||||
],
|
||||
},
|
||||
header: HeaderColors {
|
||||
tempo_bg: Color::Rgb(75, 55, 40),
|
||||
tempo_fg: orange,
|
||||
bank_bg: Color::Rgb(50, 60, 60),
|
||||
bank_fg: blue,
|
||||
pattern_bg: Color::Rgb(50, 65, 50),
|
||||
pattern_fg: aqua,
|
||||
stats_bg: bg1,
|
||||
stats_fg: fg3,
|
||||
},
|
||||
modal: ModalColors {
|
||||
border: yellow,
|
||||
border_accent: orange,
|
||||
border_warn: red,
|
||||
border_dim: fg4,
|
||||
confirm: orange,
|
||||
rename: purple,
|
||||
input: blue,
|
||||
editor: yellow,
|
||||
preview: fg4,
|
||||
},
|
||||
flash: FlashColors {
|
||||
error_bg: Color::Rgb(70, 45, 45),
|
||||
error_fg: red,
|
||||
success_bg: Color::Rgb(50, 65, 45),
|
||||
success_fg: green,
|
||||
info_bg: bg1,
|
||||
info_fg: fg,
|
||||
event_rgb: (70, 55, 45),
|
||||
},
|
||||
list: ListColors {
|
||||
playing_bg: Color::Rgb(50, 65, 45),
|
||||
playing_fg: green,
|
||||
staged_play_bg: Color::Rgb(70, 55, 60),
|
||||
staged_play_fg: purple,
|
||||
staged_stop_bg: Color::Rgb(75, 50, 50),
|
||||
staged_stop_fg: red,
|
||||
edit_bg: Color::Rgb(50, 65, 55),
|
||||
edit_fg: aqua,
|
||||
hover_bg: bg2,
|
||||
hover_fg: fg,
|
||||
},
|
||||
link_status: LinkStatusColors {
|
||||
disabled: red,
|
||||
connected: green,
|
||||
listening: yellow,
|
||||
},
|
||||
syntax: SyntaxColors {
|
||||
gap_bg: darker_bg,
|
||||
executed_bg: Color::Rgb(55, 50, 45),
|
||||
selected_bg: Color::Rgb(85, 70, 45),
|
||||
emit: (fg, Color::Rgb(80, 55, 50)),
|
||||
number: (orange, Color::Rgb(70, 50, 40)),
|
||||
string: (green, Color::Rgb(50, 60, 40)),
|
||||
comment: (fg4, darker_bg),
|
||||
keyword: (red, Color::Rgb(70, 45, 45)),
|
||||
stack_op: (blue, Color::Rgb(50, 55, 60)),
|
||||
operator: (yellow, Color::Rgb(70, 65, 40)),
|
||||
sound: (aqua, Color::Rgb(45, 60, 50)),
|
||||
param: (purple, Color::Rgb(65, 50, 55)),
|
||||
context: (orange, Color::Rgb(70, 50, 40)),
|
||||
note: (green, Color::Rgb(50, 60, 40)),
|
||||
interval: (Color::Rgb(170, 200, 100), Color::Rgb(55, 65, 40)),
|
||||
variable: (purple, Color::Rgb(65, 50, 55)),
|
||||
vary: (yellow, Color::Rgb(70, 65, 40)),
|
||||
generator: (aqua, Color::Rgb(45, 60, 50)),
|
||||
default: (fg3, darker_bg),
|
||||
},
|
||||
table: TableColors {
|
||||
row_even: darker_bg,
|
||||
row_odd: bg0,
|
||||
},
|
||||
values: ValuesColors {
|
||||
tempo: orange,
|
||||
value: fg3,
|
||||
},
|
||||
hint: HintColors {
|
||||
key: orange,
|
||||
text: fg4,
|
||||
},
|
||||
view_badge: ViewBadgeColors { bg: fg, fg: bg0 },
|
||||
nav: NavColors {
|
||||
selected_bg: Color::Rgb(80, 65, 50),
|
||||
selected_fg: fg,
|
||||
unselected_bg: bg1,
|
||||
unselected_fg: fg4,
|
||||
},
|
||||
editor_widget: EditorWidgetColors {
|
||||
cursor_bg: fg,
|
||||
cursor_fg: bg0,
|
||||
selection_bg: Color::Rgb(70, 65, 55),
|
||||
completion_bg: bg1,
|
||||
completion_fg: fg,
|
||||
completion_selected: orange,
|
||||
completion_example: aqua,
|
||||
},
|
||||
browser: BrowserColors {
|
||||
directory: blue,
|
||||
project_file: purple,
|
||||
selected: orange,
|
||||
file: fg,
|
||||
focused_border: orange,
|
||||
unfocused_border: fg4,
|
||||
root: fg,
|
||||
file_icon: fg4,
|
||||
folder_icon: blue,
|
||||
empty_text: fg4,
|
||||
},
|
||||
input: InputColors {
|
||||
text: blue,
|
||||
cursor: fg,
|
||||
hint: fg4,
|
||||
},
|
||||
search: SearchColors {
|
||||
active: orange,
|
||||
inactive: fg4,
|
||||
match_bg: yellow,
|
||||
match_fg: bg0,
|
||||
},
|
||||
markdown: MarkdownColors {
|
||||
h1: blue,
|
||||
h2: orange,
|
||||
h3: purple,
|
||||
code: green,
|
||||
code_border: Color::Rgb(80, 75, 70),
|
||||
link: aqua,
|
||||
link_url: Color::Rgb(120, 115, 105),
|
||||
quote: fg4,
|
||||
text: fg,
|
||||
list: fg,
|
||||
},
|
||||
engine: EngineColors {
|
||||
header: blue,
|
||||
header_focused: yellow,
|
||||
divider: Color::Rgb(75, 70, 65),
|
||||
scroll_indicator: Color::Rgb(90, 85, 80),
|
||||
label: Color::Rgb(145, 135, 125),
|
||||
label_focused: Color::Rgb(175, 165, 155),
|
||||
label_dim: Color::Rgb(115, 105, 95),
|
||||
value: Color::Rgb(200, 190, 175),
|
||||
focused: yellow,
|
||||
normal: fg,
|
||||
dim: Color::Rgb(90, 85, 80),
|
||||
path: Color::Rgb(145, 135, 125),
|
||||
border_magenta: purple,
|
||||
border_green: green,
|
||||
border_cyan: aqua,
|
||||
separator: Color::Rgb(75, 70, 65),
|
||||
hint_active: Color::Rgb(220, 180, 80),
|
||||
hint_inactive: Color::Rgb(75, 70, 65),
|
||||
},
|
||||
dict: DictColors {
|
||||
word_name: green,
|
||||
word_bg: Color::Rgb(55, 60, 55),
|
||||
alias: fg4,
|
||||
stack_sig: purple,
|
||||
description: fg,
|
||||
example: Color::Rgb(145, 135, 125),
|
||||
category_focused: yellow,
|
||||
category_selected: blue,
|
||||
category_normal: fg,
|
||||
category_dimmed: Color::Rgb(90, 85, 80),
|
||||
border_focused: yellow,
|
||||
border_normal: Color::Rgb(75, 70, 65),
|
||||
header_desc: Color::Rgb(165, 155, 145),
|
||||
},
|
||||
title: TitleColors {
|
||||
big_title: orange,
|
||||
author: yellow,
|
||||
link: aqua,
|
||||
license: purple,
|
||||
prompt: Color::Rgb(165, 155, 145),
|
||||
subtitle: fg,
|
||||
},
|
||||
meter: MeterColors {
|
||||
low: green,
|
||||
mid: yellow,
|
||||
high: red,
|
||||
low_rgb: (170, 175, 35),
|
||||
mid_rgb: (235, 180, 45),
|
||||
high_rgb: (240, 70, 50),
|
||||
},
|
||||
sparkle: SparkleColors {
|
||||
colors: [
|
||||
(250, 189, 47),
|
||||
(254, 128, 25),
|
||||
(184, 187, 38),
|
||||
(211, 134, 155),
|
||||
(131, 165, 152),
|
||||
],
|
||||
},
|
||||
confirm: ConfirmColors {
|
||||
border: orange,
|
||||
button_selected_bg: orange,
|
||||
button_selected_fg: bg0,
|
||||
},
|
||||
}
|
||||
}
|
||||
278
crates/ratatui/src/theme/kanagawa.rs
Normal file
278
crates/ratatui/src/theme/kanagawa.rs
Normal file
@@ -0,0 +1,278 @@
|
||||
use super::*;
|
||||
use ratatui::style::Color;
|
||||
|
||||
pub fn theme() -> ThemeColors {
|
||||
let bg = Color::Rgb(31, 31, 40);
|
||||
let bg_light = Color::Rgb(43, 43, 54);
|
||||
let bg_lighter = Color::Rgb(54, 54, 70);
|
||||
let fg = Color::Rgb(220, 215, 186);
|
||||
let fg_dim = Color::Rgb(160, 158, 140);
|
||||
let comment = Color::Rgb(114, 113, 105);
|
||||
let crystal_blue = Color::Rgb(126, 156, 216);
|
||||
let oni_violet = Color::Rgb(149, 127, 184);
|
||||
let autumn_green = Color::Rgb(118, 148, 106);
|
||||
let autumn_red = Color::Rgb(195, 64, 67);
|
||||
let carp_yellow = Color::Rgb(230, 195, 132);
|
||||
let spring_blue = Color::Rgb(127, 180, 202);
|
||||
let wave_red = Color::Rgb(226, 109, 115);
|
||||
let sakura_pink = Color::Rgb(212, 140, 149);
|
||||
|
||||
let darker_bg = Color::Rgb(26, 26, 34);
|
||||
|
||||
ThemeColors {
|
||||
ui: UiColors {
|
||||
bg,
|
||||
bg_rgb: (31, 31, 40),
|
||||
text_primary: fg,
|
||||
text_muted: fg_dim,
|
||||
text_dim: comment,
|
||||
border: bg_lighter,
|
||||
header: crystal_blue,
|
||||
unfocused: comment,
|
||||
accent: sakura_pink,
|
||||
surface: bg_light,
|
||||
},
|
||||
status: StatusColors {
|
||||
playing_bg: Color::Rgb(40, 55, 45),
|
||||
playing_fg: autumn_green,
|
||||
stopped_bg: Color::Rgb(60, 40, 45),
|
||||
stopped_fg: autumn_red,
|
||||
fill_on: autumn_green,
|
||||
fill_off: comment,
|
||||
fill_bg: bg_light,
|
||||
},
|
||||
selection: SelectionColors {
|
||||
cursor_bg: sakura_pink,
|
||||
cursor_fg: bg,
|
||||
selected_bg: Color::Rgb(65, 55, 70),
|
||||
selected_fg: sakura_pink,
|
||||
in_range_bg: Color::Rgb(50, 50, 60),
|
||||
in_range_fg: fg,
|
||||
cursor: sakura_pink,
|
||||
selected: Color::Rgb(65, 55, 70),
|
||||
in_range: Color::Rgb(50, 50, 60),
|
||||
},
|
||||
tile: TileColors {
|
||||
playing_active_bg: Color::Rgb(65, 60, 50),
|
||||
playing_active_fg: carp_yellow,
|
||||
playing_inactive_bg: Color::Rgb(55, 55, 50),
|
||||
playing_inactive_fg: fg_dim,
|
||||
active_bg: Color::Rgb(45, 55, 70),
|
||||
active_fg: crystal_blue,
|
||||
inactive_bg: bg_light,
|
||||
inactive_fg: fg_dim,
|
||||
active_selected_bg: Color::Rgb(65, 55, 70),
|
||||
active_in_range_bg: Color::Rgb(50, 50, 60),
|
||||
link_bright: [
|
||||
(226, 109, 115),
|
||||
(149, 127, 184),
|
||||
(230, 195, 132),
|
||||
(127, 180, 202),
|
||||
(118, 148, 106),
|
||||
],
|
||||
link_dim: [
|
||||
(75, 45, 50),
|
||||
(55, 50, 70),
|
||||
(70, 60, 50),
|
||||
(45, 60, 70),
|
||||
(45, 55, 45),
|
||||
],
|
||||
},
|
||||
header: HeaderColors {
|
||||
tempo_bg: Color::Rgb(55, 50, 65),
|
||||
tempo_fg: oni_violet,
|
||||
bank_bg: Color::Rgb(45, 55, 70),
|
||||
bank_fg: crystal_blue,
|
||||
pattern_bg: Color::Rgb(45, 55, 45),
|
||||
pattern_fg: autumn_green,
|
||||
stats_bg: bg_light,
|
||||
stats_fg: fg_dim,
|
||||
},
|
||||
modal: ModalColors {
|
||||
border: crystal_blue,
|
||||
border_accent: sakura_pink,
|
||||
border_warn: carp_yellow,
|
||||
border_dim: comment,
|
||||
confirm: carp_yellow,
|
||||
rename: oni_violet,
|
||||
input: crystal_blue,
|
||||
editor: crystal_blue,
|
||||
preview: comment,
|
||||
},
|
||||
flash: FlashColors {
|
||||
error_bg: Color::Rgb(60, 40, 45),
|
||||
error_fg: wave_red,
|
||||
success_bg: Color::Rgb(40, 55, 45),
|
||||
success_fg: autumn_green,
|
||||
info_bg: bg_light,
|
||||
info_fg: fg,
|
||||
event_rgb: (50, 50, 60),
|
||||
},
|
||||
list: ListColors {
|
||||
playing_bg: Color::Rgb(40, 55, 45),
|
||||
playing_fg: autumn_green,
|
||||
staged_play_bg: Color::Rgb(55, 50, 70),
|
||||
staged_play_fg: oni_violet,
|
||||
staged_stop_bg: Color::Rgb(65, 45, 50),
|
||||
staged_stop_fg: wave_red,
|
||||
edit_bg: Color::Rgb(45, 55, 70),
|
||||
edit_fg: crystal_blue,
|
||||
hover_bg: bg_lighter,
|
||||
hover_fg: fg,
|
||||
},
|
||||
link_status: LinkStatusColors {
|
||||
disabled: autumn_red,
|
||||
connected: autumn_green,
|
||||
listening: carp_yellow,
|
||||
},
|
||||
syntax: SyntaxColors {
|
||||
gap_bg: darker_bg,
|
||||
executed_bg: Color::Rgb(45, 45, 55),
|
||||
selected_bg: Color::Rgb(65, 60, 50),
|
||||
emit: (fg, Color::Rgb(60, 50, 60)),
|
||||
number: (oni_violet, Color::Rgb(55, 50, 65)),
|
||||
string: (autumn_green, Color::Rgb(45, 55, 45)),
|
||||
comment: (comment, darker_bg),
|
||||
keyword: (sakura_pink, Color::Rgb(60, 50, 55)),
|
||||
stack_op: (spring_blue, Color::Rgb(45, 55, 65)),
|
||||
operator: (wave_red, Color::Rgb(60, 45, 50)),
|
||||
sound: (crystal_blue, Color::Rgb(45, 55, 70)),
|
||||
param: (carp_yellow, Color::Rgb(65, 60, 50)),
|
||||
context: (carp_yellow, Color::Rgb(65, 60, 50)),
|
||||
note: (autumn_green, Color::Rgb(45, 55, 45)),
|
||||
interval: (Color::Rgb(150, 180, 130), Color::Rgb(45, 55, 45)),
|
||||
variable: (autumn_green, Color::Rgb(45, 55, 45)),
|
||||
vary: (carp_yellow, Color::Rgb(65, 60, 50)),
|
||||
generator: (spring_blue, Color::Rgb(45, 55, 65)),
|
||||
default: (fg_dim, darker_bg),
|
||||
},
|
||||
table: TableColors {
|
||||
row_even: darker_bg,
|
||||
row_odd: bg,
|
||||
},
|
||||
values: ValuesColors {
|
||||
tempo: carp_yellow,
|
||||
value: fg_dim,
|
||||
},
|
||||
hint: HintColors {
|
||||
key: carp_yellow,
|
||||
text: comment,
|
||||
},
|
||||
view_badge: ViewBadgeColors { bg: fg, fg: bg },
|
||||
nav: NavColors {
|
||||
selected_bg: Color::Rgb(60, 50, 70),
|
||||
selected_fg: fg,
|
||||
unselected_bg: bg_light,
|
||||
unselected_fg: comment,
|
||||
},
|
||||
editor_widget: EditorWidgetColors {
|
||||
cursor_bg: fg,
|
||||
cursor_fg: bg,
|
||||
selection_bg: Color::Rgb(55, 55, 70),
|
||||
completion_bg: bg_light,
|
||||
completion_fg: fg,
|
||||
completion_selected: carp_yellow,
|
||||
completion_example: spring_blue,
|
||||
},
|
||||
browser: BrowserColors {
|
||||
directory: crystal_blue,
|
||||
project_file: oni_violet,
|
||||
selected: carp_yellow,
|
||||
file: fg,
|
||||
focused_border: carp_yellow,
|
||||
unfocused_border: comment,
|
||||
root: fg,
|
||||
file_icon: comment,
|
||||
folder_icon: crystal_blue,
|
||||
empty_text: comment,
|
||||
},
|
||||
input: InputColors {
|
||||
text: crystal_blue,
|
||||
cursor: fg,
|
||||
hint: comment,
|
||||
},
|
||||
search: SearchColors {
|
||||
active: carp_yellow,
|
||||
inactive: comment,
|
||||
match_bg: carp_yellow,
|
||||
match_fg: bg,
|
||||
},
|
||||
markdown: MarkdownColors {
|
||||
h1: crystal_blue,
|
||||
h2: carp_yellow,
|
||||
h3: oni_violet,
|
||||
code: autumn_green,
|
||||
code_border: Color::Rgb(65, 65, 80),
|
||||
link: sakura_pink,
|
||||
link_url: Color::Rgb(100, 100, 115),
|
||||
quote: comment,
|
||||
text: fg,
|
||||
list: fg,
|
||||
},
|
||||
engine: EngineColors {
|
||||
header: crystal_blue,
|
||||
header_focused: carp_yellow,
|
||||
divider: Color::Rgb(60, 60, 75),
|
||||
scroll_indicator: Color::Rgb(75, 75, 92),
|
||||
label: Color::Rgb(140, 138, 125),
|
||||
label_focused: Color::Rgb(170, 168, 155),
|
||||
label_dim: Color::Rgb(110, 108, 100),
|
||||
value: Color::Rgb(200, 195, 175),
|
||||
focused: carp_yellow,
|
||||
normal: fg,
|
||||
dim: Color::Rgb(75, 75, 92),
|
||||
path: Color::Rgb(140, 138, 125),
|
||||
border_magenta: oni_violet,
|
||||
border_green: autumn_green,
|
||||
border_cyan: spring_blue,
|
||||
separator: Color::Rgb(60, 60, 75),
|
||||
hint_active: Color::Rgb(220, 185, 120),
|
||||
hint_inactive: Color::Rgb(60, 60, 75),
|
||||
},
|
||||
dict: DictColors {
|
||||
word_name: autumn_green,
|
||||
word_bg: Color::Rgb(45, 50, 50),
|
||||
alias: comment,
|
||||
stack_sig: oni_violet,
|
||||
description: fg,
|
||||
example: Color::Rgb(140, 138, 125),
|
||||
category_focused: carp_yellow,
|
||||
category_selected: crystal_blue,
|
||||
category_normal: fg,
|
||||
category_dimmed: Color::Rgb(75, 75, 92),
|
||||
border_focused: carp_yellow,
|
||||
border_normal: Color::Rgb(60, 60, 75),
|
||||
header_desc: Color::Rgb(160, 158, 145),
|
||||
},
|
||||
title: TitleColors {
|
||||
big_title: sakura_pink,
|
||||
author: crystal_blue,
|
||||
link: autumn_green,
|
||||
license: carp_yellow,
|
||||
prompt: Color::Rgb(160, 158, 145),
|
||||
subtitle: fg,
|
||||
},
|
||||
meter: MeterColors {
|
||||
low: autumn_green,
|
||||
mid: carp_yellow,
|
||||
high: wave_red,
|
||||
low_rgb: (118, 148, 106),
|
||||
mid_rgb: (230, 195, 132),
|
||||
high_rgb: (226, 109, 115),
|
||||
},
|
||||
sparkle: SparkleColors {
|
||||
colors: [
|
||||
(127, 180, 202),
|
||||
(230, 195, 132),
|
||||
(118, 148, 106),
|
||||
(226, 109, 115),
|
||||
(149, 127, 184),
|
||||
],
|
||||
},
|
||||
confirm: ConfirmColors {
|
||||
border: carp_yellow,
|
||||
button_selected_bg: carp_yellow,
|
||||
button_selected_fg: bg,
|
||||
},
|
||||
}
|
||||
}
|
||||
373
crates/ratatui/src/theme/mod.rs
Normal file
373
crates/ratatui/src/theme/mod.rs
Normal file
@@ -0,0 +1,373 @@
|
||||
//! Centralized color definitions for Cagire TUI.
|
||||
//! Supports multiple color schemes with runtime switching.
|
||||
|
||||
mod catppuccin_latte;
|
||||
mod catppuccin_mocha;
|
||||
mod dracula;
|
||||
mod gruvbox_dark;
|
||||
mod kanagawa;
|
||||
mod monochrome_black;
|
||||
mod monochrome_white;
|
||||
mod monokai;
|
||||
mod nord;
|
||||
mod pitch_black;
|
||||
mod rose_pine;
|
||||
mod tokyo_night;
|
||||
|
||||
use ratatui::style::Color;
|
||||
use std::cell::RefCell;
|
||||
|
||||
pub struct ThemeEntry {
|
||||
pub id: &'static str,
|
||||
pub label: &'static str,
|
||||
pub colors: fn() -> ThemeColors,
|
||||
}
|
||||
|
||||
pub const THEMES: &[ThemeEntry] = &[
|
||||
ThemeEntry { id: "CatppuccinMocha", label: "Catppuccin Mocha", colors: catppuccin_mocha::theme },
|
||||
ThemeEntry { id: "CatppuccinLatte", label: "Catppuccin Latte", colors: catppuccin_latte::theme },
|
||||
ThemeEntry { id: "Nord", label: "Nord", colors: nord::theme },
|
||||
ThemeEntry { id: "Dracula", label: "Dracula", colors: dracula::theme },
|
||||
ThemeEntry { id: "GruvboxDark", label: "Gruvbox Dark", colors: gruvbox_dark::theme },
|
||||
ThemeEntry { id: "Monokai", label: "Monokai", colors: monokai::theme },
|
||||
ThemeEntry { id: "MonochromeBlack", label: "Monochrome (Black)", colors: monochrome_black::theme },
|
||||
ThemeEntry { id: "MonochromeWhite", label: "Monochrome (White)", colors: monochrome_white::theme },
|
||||
ThemeEntry { id: "PitchBlack", label: "Pitch Black", colors: pitch_black::theme },
|
||||
ThemeEntry { id: "TokyoNight", label: "Tokyo Night", colors: tokyo_night::theme },
|
||||
ThemeEntry { id: "RosePine", label: "Rosé Pine", colors: rose_pine::theme },
|
||||
ThemeEntry { id: "Kanagawa", label: "Kanagawa", colors: kanagawa::theme },
|
||||
];
|
||||
|
||||
thread_local! {
|
||||
static CURRENT_THEME: RefCell<ThemeColors> = RefCell::new((THEMES[0].colors)());
|
||||
}
|
||||
|
||||
pub fn get() -> ThemeColors {
|
||||
CURRENT_THEME.with(|t| t.borrow().clone())
|
||||
}
|
||||
|
||||
pub fn set(theme: ThemeColors) {
|
||||
CURRENT_THEME.with(|t| *t.borrow_mut() = theme);
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ThemeColors {
|
||||
pub ui: UiColors,
|
||||
pub status: StatusColors,
|
||||
pub selection: SelectionColors,
|
||||
pub tile: TileColors,
|
||||
pub header: HeaderColors,
|
||||
pub modal: ModalColors,
|
||||
pub flash: FlashColors,
|
||||
pub list: ListColors,
|
||||
pub link_status: LinkStatusColors,
|
||||
pub syntax: SyntaxColors,
|
||||
pub table: TableColors,
|
||||
pub values: ValuesColors,
|
||||
pub hint: HintColors,
|
||||
pub view_badge: ViewBadgeColors,
|
||||
pub nav: NavColors,
|
||||
pub editor_widget: EditorWidgetColors,
|
||||
pub browser: BrowserColors,
|
||||
pub input: InputColors,
|
||||
pub search: SearchColors,
|
||||
pub markdown: MarkdownColors,
|
||||
pub engine: EngineColors,
|
||||
pub dict: DictColors,
|
||||
pub title: TitleColors,
|
||||
pub meter: MeterColors,
|
||||
pub sparkle: SparkleColors,
|
||||
pub confirm: ConfirmColors,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct UiColors {
|
||||
pub bg: Color,
|
||||
pub bg_rgb: (u8, u8, u8),
|
||||
pub text_primary: Color,
|
||||
pub text_muted: Color,
|
||||
pub text_dim: Color,
|
||||
pub border: Color,
|
||||
pub header: Color,
|
||||
pub unfocused: Color,
|
||||
pub accent: Color,
|
||||
pub surface: Color,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct StatusColors {
|
||||
pub playing_bg: Color,
|
||||
pub playing_fg: Color,
|
||||
pub stopped_bg: Color,
|
||||
pub stopped_fg: Color,
|
||||
pub fill_on: Color,
|
||||
pub fill_off: Color,
|
||||
pub fill_bg: Color,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct SelectionColors {
|
||||
pub cursor_bg: Color,
|
||||
pub cursor_fg: Color,
|
||||
pub selected_bg: Color,
|
||||
pub selected_fg: Color,
|
||||
pub in_range_bg: Color,
|
||||
pub in_range_fg: Color,
|
||||
pub cursor: Color,
|
||||
pub selected: Color,
|
||||
pub in_range: Color,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct TileColors {
|
||||
pub playing_active_bg: Color,
|
||||
pub playing_active_fg: Color,
|
||||
pub playing_inactive_bg: Color,
|
||||
pub playing_inactive_fg: Color,
|
||||
pub active_bg: Color,
|
||||
pub active_fg: Color,
|
||||
pub inactive_bg: Color,
|
||||
pub inactive_fg: Color,
|
||||
pub active_selected_bg: Color,
|
||||
pub active_in_range_bg: Color,
|
||||
pub link_bright: [(u8, u8, u8); 5],
|
||||
pub link_dim: [(u8, u8, u8); 5],
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct HeaderColors {
|
||||
pub tempo_bg: Color,
|
||||
pub tempo_fg: Color,
|
||||
pub bank_bg: Color,
|
||||
pub bank_fg: Color,
|
||||
pub pattern_bg: Color,
|
||||
pub pattern_fg: Color,
|
||||
pub stats_bg: Color,
|
||||
pub stats_fg: Color,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ModalColors {
|
||||
pub border: Color,
|
||||
pub border_accent: Color,
|
||||
pub border_warn: Color,
|
||||
pub border_dim: Color,
|
||||
pub confirm: Color,
|
||||
pub rename: Color,
|
||||
pub input: Color,
|
||||
pub editor: Color,
|
||||
pub preview: Color,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct FlashColors {
|
||||
pub error_bg: Color,
|
||||
pub error_fg: Color,
|
||||
pub success_bg: Color,
|
||||
pub success_fg: Color,
|
||||
pub info_bg: Color,
|
||||
pub info_fg: Color,
|
||||
pub event_rgb: (u8, u8, u8),
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ListColors {
|
||||
pub playing_bg: Color,
|
||||
pub playing_fg: Color,
|
||||
pub staged_play_bg: Color,
|
||||
pub staged_play_fg: Color,
|
||||
pub staged_stop_bg: Color,
|
||||
pub staged_stop_fg: Color,
|
||||
pub edit_bg: Color,
|
||||
pub edit_fg: Color,
|
||||
pub hover_bg: Color,
|
||||
pub hover_fg: Color,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct LinkStatusColors {
|
||||
pub disabled: Color,
|
||||
pub connected: Color,
|
||||
pub listening: Color,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct SyntaxColors {
|
||||
pub gap_bg: Color,
|
||||
pub executed_bg: Color,
|
||||
pub selected_bg: Color,
|
||||
pub emit: (Color, Color),
|
||||
pub number: (Color, Color),
|
||||
pub string: (Color, Color),
|
||||
pub comment: (Color, Color),
|
||||
pub keyword: (Color, Color),
|
||||
pub stack_op: (Color, Color),
|
||||
pub operator: (Color, Color),
|
||||
pub sound: (Color, Color),
|
||||
pub param: (Color, Color),
|
||||
pub context: (Color, Color),
|
||||
pub note: (Color, Color),
|
||||
pub interval: (Color, Color),
|
||||
pub variable: (Color, Color),
|
||||
pub vary: (Color, Color),
|
||||
pub generator: (Color, Color),
|
||||
pub default: (Color, Color),
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct TableColors {
|
||||
pub row_even: Color,
|
||||
pub row_odd: Color,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ValuesColors {
|
||||
pub tempo: Color,
|
||||
pub value: Color,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct HintColors {
|
||||
pub key: Color,
|
||||
pub text: Color,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ViewBadgeColors {
|
||||
pub bg: Color,
|
||||
pub fg: Color,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct NavColors {
|
||||
pub selected_bg: Color,
|
||||
pub selected_fg: Color,
|
||||
pub unselected_bg: Color,
|
||||
pub unselected_fg: Color,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct EditorWidgetColors {
|
||||
pub cursor_bg: Color,
|
||||
pub cursor_fg: Color,
|
||||
pub selection_bg: Color,
|
||||
pub completion_bg: Color,
|
||||
pub completion_fg: Color,
|
||||
pub completion_selected: Color,
|
||||
pub completion_example: Color,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct BrowserColors {
|
||||
pub directory: Color,
|
||||
pub project_file: Color,
|
||||
pub selected: Color,
|
||||
pub file: Color,
|
||||
pub focused_border: Color,
|
||||
pub unfocused_border: Color,
|
||||
pub root: Color,
|
||||
pub file_icon: Color,
|
||||
pub folder_icon: Color,
|
||||
pub empty_text: Color,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct InputColors {
|
||||
pub text: Color,
|
||||
pub cursor: Color,
|
||||
pub hint: Color,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct SearchColors {
|
||||
pub active: Color,
|
||||
pub inactive: Color,
|
||||
pub match_bg: Color,
|
||||
pub match_fg: Color,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct MarkdownColors {
|
||||
pub h1: Color,
|
||||
pub h2: Color,
|
||||
pub h3: Color,
|
||||
pub code: Color,
|
||||
pub code_border: Color,
|
||||
pub link: Color,
|
||||
pub link_url: Color,
|
||||
pub quote: Color,
|
||||
pub text: Color,
|
||||
pub list: Color,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct EngineColors {
|
||||
pub header: Color,
|
||||
pub header_focused: Color,
|
||||
pub divider: Color,
|
||||
pub scroll_indicator: Color,
|
||||
pub label: Color,
|
||||
pub label_focused: Color,
|
||||
pub label_dim: Color,
|
||||
pub value: Color,
|
||||
pub focused: Color,
|
||||
pub normal: Color,
|
||||
pub dim: Color,
|
||||
pub path: Color,
|
||||
pub border_magenta: Color,
|
||||
pub border_green: Color,
|
||||
pub border_cyan: Color,
|
||||
pub separator: Color,
|
||||
pub hint_active: Color,
|
||||
pub hint_inactive: Color,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct DictColors {
|
||||
pub word_name: Color,
|
||||
pub word_bg: Color,
|
||||
pub alias: Color,
|
||||
pub stack_sig: Color,
|
||||
pub description: Color,
|
||||
pub example: Color,
|
||||
pub category_focused: Color,
|
||||
pub category_selected: Color,
|
||||
pub category_normal: Color,
|
||||
pub category_dimmed: Color,
|
||||
pub border_focused: Color,
|
||||
pub border_normal: Color,
|
||||
pub header_desc: Color,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct TitleColors {
|
||||
pub big_title: Color,
|
||||
pub author: Color,
|
||||
pub link: Color,
|
||||
pub license: Color,
|
||||
pub prompt: Color,
|
||||
pub subtitle: Color,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct MeterColors {
|
||||
pub low: Color,
|
||||
pub mid: Color,
|
||||
pub high: Color,
|
||||
pub low_rgb: (u8, u8, u8),
|
||||
pub mid_rgb: (u8, u8, u8),
|
||||
pub high_rgb: (u8, u8, u8),
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct SparkleColors {
|
||||
pub colors: [(u8, u8, u8); 5],
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ConfirmColors {
|
||||
pub border: Color,
|
||||
pub button_selected_bg: Color,
|
||||
pub button_selected_fg: Color,
|
||||
}
|
||||
|
||||
275
crates/ratatui/src/theme/monochrome_black.rs
Normal file
275
crates/ratatui/src/theme/monochrome_black.rs
Normal file
@@ -0,0 +1,275 @@
|
||||
use super::*;
|
||||
use ratatui::style::Color;
|
||||
|
||||
pub fn theme() -> ThemeColors {
|
||||
let bg = Color::Rgb(0, 0, 0);
|
||||
let surface = Color::Rgb(18, 18, 18);
|
||||
let surface2 = Color::Rgb(30, 30, 30);
|
||||
let border = Color::Rgb(60, 60, 60);
|
||||
let fg = Color::Rgb(255, 255, 255);
|
||||
let fg_dim = Color::Rgb(180, 180, 180);
|
||||
let fg_muted = Color::Rgb(120, 120, 120);
|
||||
|
||||
let bright = Color::Rgb(255, 255, 255);
|
||||
let medium = Color::Rgb(180, 180, 180);
|
||||
let dim = Color::Rgb(120, 120, 120);
|
||||
let dark = Color::Rgb(80, 80, 80);
|
||||
let darker = Color::Rgb(50, 50, 50);
|
||||
|
||||
ThemeColors {
|
||||
ui: UiColors {
|
||||
bg,
|
||||
bg_rgb: (0, 0, 0),
|
||||
text_primary: fg,
|
||||
text_muted: fg_dim,
|
||||
text_dim: fg_muted,
|
||||
border,
|
||||
header: bright,
|
||||
unfocused: fg_muted,
|
||||
accent: bright,
|
||||
surface,
|
||||
},
|
||||
status: StatusColors {
|
||||
playing_bg: Color::Rgb(40, 40, 40),
|
||||
playing_fg: bright,
|
||||
stopped_bg: Color::Rgb(25, 25, 25),
|
||||
stopped_fg: medium,
|
||||
fill_on: bright,
|
||||
fill_off: dark,
|
||||
fill_bg: surface,
|
||||
},
|
||||
selection: SelectionColors {
|
||||
cursor_bg: bright,
|
||||
cursor_fg: bg,
|
||||
selected_bg: Color::Rgb(60, 60, 60),
|
||||
selected_fg: bright,
|
||||
in_range_bg: Color::Rgb(40, 40, 40),
|
||||
in_range_fg: fg,
|
||||
cursor: bright,
|
||||
selected: Color::Rgb(60, 60, 60),
|
||||
in_range: Color::Rgb(40, 40, 40),
|
||||
},
|
||||
tile: TileColors {
|
||||
playing_active_bg: Color::Rgb(70, 70, 70),
|
||||
playing_active_fg: bright,
|
||||
playing_inactive_bg: Color::Rgb(50, 50, 50),
|
||||
playing_inactive_fg: medium,
|
||||
active_bg: Color::Rgb(45, 45, 45),
|
||||
active_fg: bright,
|
||||
inactive_bg: surface,
|
||||
inactive_fg: fg_dim,
|
||||
active_selected_bg: Color::Rgb(80, 80, 80),
|
||||
active_in_range_bg: Color::Rgb(55, 55, 55),
|
||||
link_bright: [
|
||||
(255, 255, 255),
|
||||
(200, 200, 200),
|
||||
(160, 160, 160),
|
||||
(220, 220, 220),
|
||||
(180, 180, 180),
|
||||
],
|
||||
link_dim: [
|
||||
(60, 60, 60),
|
||||
(50, 50, 50),
|
||||
(45, 45, 45),
|
||||
(55, 55, 55),
|
||||
(48, 48, 48),
|
||||
],
|
||||
},
|
||||
header: HeaderColors {
|
||||
tempo_bg: Color::Rgb(50, 50, 50),
|
||||
tempo_fg: bright,
|
||||
bank_bg: Color::Rgb(40, 40, 40),
|
||||
bank_fg: medium,
|
||||
pattern_bg: Color::Rgb(35, 35, 35),
|
||||
pattern_fg: medium,
|
||||
stats_bg: surface,
|
||||
stats_fg: fg_dim,
|
||||
},
|
||||
modal: ModalColors {
|
||||
border: bright,
|
||||
border_accent: medium,
|
||||
border_warn: fg_dim,
|
||||
border_dim: fg_muted,
|
||||
confirm: medium,
|
||||
rename: medium,
|
||||
input: bright,
|
||||
editor: bright,
|
||||
preview: fg_muted,
|
||||
},
|
||||
flash: FlashColors {
|
||||
error_bg: Color::Rgb(60, 60, 60),
|
||||
error_fg: bright,
|
||||
success_bg: Color::Rgb(50, 50, 50),
|
||||
success_fg: bright,
|
||||
info_bg: surface,
|
||||
info_fg: fg,
|
||||
event_rgb: (40, 40, 40),
|
||||
},
|
||||
list: ListColors {
|
||||
playing_bg: Color::Rgb(50, 50, 50),
|
||||
playing_fg: bright,
|
||||
staged_play_bg: Color::Rgb(45, 45, 45),
|
||||
staged_play_fg: medium,
|
||||
staged_stop_bg: Color::Rgb(35, 35, 35),
|
||||
staged_stop_fg: dim,
|
||||
edit_bg: Color::Rgb(40, 40, 40),
|
||||
edit_fg: bright,
|
||||
hover_bg: surface2,
|
||||
hover_fg: fg,
|
||||
},
|
||||
link_status: LinkStatusColors {
|
||||
disabled: dim,
|
||||
connected: bright,
|
||||
listening: medium,
|
||||
},
|
||||
syntax: SyntaxColors {
|
||||
gap_bg: bg,
|
||||
executed_bg: Color::Rgb(35, 35, 35),
|
||||
selected_bg: Color::Rgb(55, 55, 55),
|
||||
emit: (bright, Color::Rgb(45, 45, 45)),
|
||||
number: (medium, Color::Rgb(35, 35, 35)),
|
||||
string: (bright, Color::Rgb(40, 40, 40)),
|
||||
comment: (dark, bg),
|
||||
keyword: (bright, Color::Rgb(50, 50, 50)),
|
||||
stack_op: (medium, Color::Rgb(30, 30, 30)),
|
||||
operator: (medium, Color::Rgb(35, 35, 35)),
|
||||
sound: (bright, Color::Rgb(45, 45, 45)),
|
||||
param: (medium, Color::Rgb(35, 35, 35)),
|
||||
context: (medium, Color::Rgb(30, 30, 30)),
|
||||
note: (bright, Color::Rgb(40, 40, 40)),
|
||||
interval: (medium, Color::Rgb(35, 35, 35)),
|
||||
variable: (medium, Color::Rgb(30, 30, 30)),
|
||||
vary: (dim, Color::Rgb(25, 25, 25)),
|
||||
generator: (bright, Color::Rgb(45, 45, 45)),
|
||||
default: (fg_dim, bg),
|
||||
},
|
||||
table: TableColors {
|
||||
row_even: bg,
|
||||
row_odd: surface,
|
||||
},
|
||||
values: ValuesColors {
|
||||
tempo: bright,
|
||||
value: fg_dim,
|
||||
},
|
||||
hint: HintColors {
|
||||
key: bright,
|
||||
text: fg_muted,
|
||||
},
|
||||
view_badge: ViewBadgeColors { bg: fg, fg: bg },
|
||||
nav: NavColors {
|
||||
selected_bg: Color::Rgb(60, 60, 60),
|
||||
selected_fg: fg,
|
||||
unselected_bg: surface,
|
||||
unselected_fg: fg_muted,
|
||||
},
|
||||
editor_widget: EditorWidgetColors {
|
||||
cursor_bg: fg,
|
||||
cursor_fg: bg,
|
||||
selection_bg: Color::Rgb(60, 60, 60),
|
||||
completion_bg: surface,
|
||||
completion_fg: fg,
|
||||
completion_selected: bright,
|
||||
completion_example: medium,
|
||||
},
|
||||
browser: BrowserColors {
|
||||
directory: medium,
|
||||
project_file: bright,
|
||||
selected: bright,
|
||||
file: fg,
|
||||
focused_border: bright,
|
||||
unfocused_border: fg_muted,
|
||||
root: fg,
|
||||
file_icon: fg_muted,
|
||||
folder_icon: medium,
|
||||
empty_text: fg_muted,
|
||||
},
|
||||
input: InputColors {
|
||||
text: bright,
|
||||
cursor: fg,
|
||||
hint: fg_muted,
|
||||
},
|
||||
search: SearchColors {
|
||||
active: bright,
|
||||
inactive: fg_muted,
|
||||
match_bg: bright,
|
||||
match_fg: bg,
|
||||
},
|
||||
markdown: MarkdownColors {
|
||||
h1: bright,
|
||||
h2: medium,
|
||||
h3: dim,
|
||||
code: medium,
|
||||
code_border: Color::Rgb(60, 60, 60),
|
||||
link: bright,
|
||||
link_url: dim,
|
||||
quote: fg_muted,
|
||||
text: fg,
|
||||
list: fg,
|
||||
},
|
||||
engine: EngineColors {
|
||||
header: bright,
|
||||
header_focused: bright,
|
||||
divider: Color::Rgb(50, 50, 50),
|
||||
scroll_indicator: Color::Rgb(70, 70, 70),
|
||||
label: dim,
|
||||
label_focused: medium,
|
||||
label_dim: dark,
|
||||
value: fg,
|
||||
focused: bright,
|
||||
normal: fg,
|
||||
dim: dark,
|
||||
path: dim,
|
||||
border_magenta: medium,
|
||||
border_green: medium,
|
||||
border_cyan: medium,
|
||||
separator: Color::Rgb(50, 50, 50),
|
||||
hint_active: bright,
|
||||
hint_inactive: darker,
|
||||
},
|
||||
dict: DictColors {
|
||||
word_name: bright,
|
||||
word_bg: Color::Rgb(30, 30, 30),
|
||||
alias: fg_muted,
|
||||
stack_sig: medium,
|
||||
description: fg,
|
||||
example: dim,
|
||||
category_focused: bright,
|
||||
category_selected: medium,
|
||||
category_normal: fg,
|
||||
category_dimmed: dark,
|
||||
border_focused: bright,
|
||||
border_normal: darker,
|
||||
header_desc: dim,
|
||||
},
|
||||
title: TitleColors {
|
||||
big_title: bright,
|
||||
author: medium,
|
||||
link: medium,
|
||||
license: dim,
|
||||
prompt: dim,
|
||||
subtitle: fg,
|
||||
},
|
||||
meter: MeterColors {
|
||||
low: dim,
|
||||
mid: medium,
|
||||
high: bright,
|
||||
low_rgb: (120, 120, 120),
|
||||
mid_rgb: (180, 180, 180),
|
||||
high_rgb: (255, 255, 255),
|
||||
},
|
||||
sparkle: SparkleColors {
|
||||
colors: [
|
||||
(255, 255, 255),
|
||||
(200, 200, 200),
|
||||
(160, 160, 160),
|
||||
(220, 220, 220),
|
||||
(180, 180, 180),
|
||||
],
|
||||
},
|
||||
confirm: ConfirmColors {
|
||||
border: bright,
|
||||
button_selected_bg: bright,
|
||||
button_selected_fg: bg,
|
||||
},
|
||||
}
|
||||
}
|
||||
275
crates/ratatui/src/theme/monochrome_white.rs
Normal file
275
crates/ratatui/src/theme/monochrome_white.rs
Normal file
@@ -0,0 +1,275 @@
|
||||
use super::*;
|
||||
use ratatui::style::Color;
|
||||
|
||||
pub fn theme() -> ThemeColors {
|
||||
let bg = Color::Rgb(255, 255, 255);
|
||||
let surface = Color::Rgb(240, 240, 240);
|
||||
let surface2 = Color::Rgb(225, 225, 225);
|
||||
let border = Color::Rgb(180, 180, 180);
|
||||
let fg = Color::Rgb(0, 0, 0);
|
||||
let fg_dim = Color::Rgb(80, 80, 80);
|
||||
let fg_muted = Color::Rgb(140, 140, 140);
|
||||
|
||||
let dark = Color::Rgb(0, 0, 0);
|
||||
let medium = Color::Rgb(80, 80, 80);
|
||||
let dim = Color::Rgb(140, 140, 140);
|
||||
let light = Color::Rgb(180, 180, 180);
|
||||
let lighter = Color::Rgb(210, 210, 210);
|
||||
|
||||
ThemeColors {
|
||||
ui: UiColors {
|
||||
bg,
|
||||
bg_rgb: (255, 255, 255),
|
||||
text_primary: fg,
|
||||
text_muted: fg_dim,
|
||||
text_dim: fg_muted,
|
||||
border,
|
||||
header: dark,
|
||||
unfocused: fg_muted,
|
||||
accent: dark,
|
||||
surface,
|
||||
},
|
||||
status: StatusColors {
|
||||
playing_bg: Color::Rgb(210, 210, 210),
|
||||
playing_fg: dark,
|
||||
stopped_bg: Color::Rgb(230, 230, 230),
|
||||
stopped_fg: medium,
|
||||
fill_on: dark,
|
||||
fill_off: light,
|
||||
fill_bg: surface,
|
||||
},
|
||||
selection: SelectionColors {
|
||||
cursor_bg: dark,
|
||||
cursor_fg: bg,
|
||||
selected_bg: Color::Rgb(200, 200, 200),
|
||||
selected_fg: dark,
|
||||
in_range_bg: Color::Rgb(220, 220, 220),
|
||||
in_range_fg: fg,
|
||||
cursor: dark,
|
||||
selected: Color::Rgb(200, 200, 200),
|
||||
in_range: Color::Rgb(220, 220, 220),
|
||||
},
|
||||
tile: TileColors {
|
||||
playing_active_bg: Color::Rgb(180, 180, 180),
|
||||
playing_active_fg: dark,
|
||||
playing_inactive_bg: Color::Rgb(200, 200, 200),
|
||||
playing_inactive_fg: medium,
|
||||
active_bg: Color::Rgb(210, 210, 210),
|
||||
active_fg: dark,
|
||||
inactive_bg: surface,
|
||||
inactive_fg: fg_dim,
|
||||
active_selected_bg: Color::Rgb(170, 170, 170),
|
||||
active_in_range_bg: Color::Rgb(195, 195, 195),
|
||||
link_bright: [
|
||||
(0, 0, 0),
|
||||
(60, 60, 60),
|
||||
(100, 100, 100),
|
||||
(40, 40, 40),
|
||||
(80, 80, 80),
|
||||
],
|
||||
link_dim: [
|
||||
(200, 200, 200),
|
||||
(210, 210, 210),
|
||||
(215, 215, 215),
|
||||
(205, 205, 205),
|
||||
(212, 212, 212),
|
||||
],
|
||||
},
|
||||
header: HeaderColors {
|
||||
tempo_bg: Color::Rgb(200, 200, 200),
|
||||
tempo_fg: dark,
|
||||
bank_bg: Color::Rgb(215, 215, 215),
|
||||
bank_fg: medium,
|
||||
pattern_bg: Color::Rgb(220, 220, 220),
|
||||
pattern_fg: medium,
|
||||
stats_bg: surface,
|
||||
stats_fg: fg_dim,
|
||||
},
|
||||
modal: ModalColors {
|
||||
border: dark,
|
||||
border_accent: medium,
|
||||
border_warn: fg_dim,
|
||||
border_dim: fg_muted,
|
||||
confirm: medium,
|
||||
rename: medium,
|
||||
input: dark,
|
||||
editor: dark,
|
||||
preview: fg_muted,
|
||||
},
|
||||
flash: FlashColors {
|
||||
error_bg: Color::Rgb(200, 200, 200),
|
||||
error_fg: dark,
|
||||
success_bg: Color::Rgb(210, 210, 210),
|
||||
success_fg: dark,
|
||||
info_bg: surface,
|
||||
info_fg: fg,
|
||||
event_rgb: (220, 220, 220),
|
||||
},
|
||||
list: ListColors {
|
||||
playing_bg: Color::Rgb(200, 200, 200),
|
||||
playing_fg: dark,
|
||||
staged_play_bg: Color::Rgb(210, 210, 210),
|
||||
staged_play_fg: medium,
|
||||
staged_stop_bg: Color::Rgb(220, 220, 220),
|
||||
staged_stop_fg: dim,
|
||||
edit_bg: Color::Rgb(215, 215, 215),
|
||||
edit_fg: dark,
|
||||
hover_bg: surface2,
|
||||
hover_fg: fg,
|
||||
},
|
||||
link_status: LinkStatusColors {
|
||||
disabled: dim,
|
||||
connected: dark,
|
||||
listening: medium,
|
||||
},
|
||||
syntax: SyntaxColors {
|
||||
gap_bg: bg,
|
||||
executed_bg: Color::Rgb(220, 220, 220),
|
||||
selected_bg: Color::Rgb(200, 200, 200),
|
||||
emit: (dark, Color::Rgb(215, 215, 215)),
|
||||
number: (medium, Color::Rgb(225, 225, 225)),
|
||||
string: (dark, Color::Rgb(220, 220, 220)),
|
||||
comment: (light, bg),
|
||||
keyword: (dark, Color::Rgb(205, 205, 205)),
|
||||
stack_op: (medium, Color::Rgb(230, 230, 230)),
|
||||
operator: (medium, Color::Rgb(225, 225, 225)),
|
||||
sound: (dark, Color::Rgb(215, 215, 215)),
|
||||
param: (medium, Color::Rgb(225, 225, 225)),
|
||||
context: (medium, Color::Rgb(230, 230, 230)),
|
||||
note: (dark, Color::Rgb(220, 220, 220)),
|
||||
interval: (medium, Color::Rgb(225, 225, 225)),
|
||||
variable: (medium, Color::Rgb(230, 230, 230)),
|
||||
vary: (dim, Color::Rgb(235, 235, 235)),
|
||||
generator: (dark, Color::Rgb(215, 215, 215)),
|
||||
default: (fg_dim, bg),
|
||||
},
|
||||
table: TableColors {
|
||||
row_even: bg,
|
||||
row_odd: surface,
|
||||
},
|
||||
values: ValuesColors {
|
||||
tempo: dark,
|
||||
value: fg_dim,
|
||||
},
|
||||
hint: HintColors {
|
||||
key: dark,
|
||||
text: fg_muted,
|
||||
},
|
||||
view_badge: ViewBadgeColors { bg: fg, fg: bg },
|
||||
nav: NavColors {
|
||||
selected_bg: Color::Rgb(200, 200, 200),
|
||||
selected_fg: fg,
|
||||
unselected_bg: surface,
|
||||
unselected_fg: fg_muted,
|
||||
},
|
||||
editor_widget: EditorWidgetColors {
|
||||
cursor_bg: fg,
|
||||
cursor_fg: bg,
|
||||
selection_bg: Color::Rgb(200, 200, 200),
|
||||
completion_bg: surface,
|
||||
completion_fg: fg,
|
||||
completion_selected: dark,
|
||||
completion_example: medium,
|
||||
},
|
||||
browser: BrowserColors {
|
||||
directory: medium,
|
||||
project_file: dark,
|
||||
selected: dark,
|
||||
file: fg,
|
||||
focused_border: dark,
|
||||
unfocused_border: fg_muted,
|
||||
root: fg,
|
||||
file_icon: fg_muted,
|
||||
folder_icon: medium,
|
||||
empty_text: fg_muted,
|
||||
},
|
||||
input: InputColors {
|
||||
text: dark,
|
||||
cursor: fg,
|
||||
hint: fg_muted,
|
||||
},
|
||||
search: SearchColors {
|
||||
active: dark,
|
||||
inactive: fg_muted,
|
||||
match_bg: dark,
|
||||
match_fg: bg,
|
||||
},
|
||||
markdown: MarkdownColors {
|
||||
h1: dark,
|
||||
h2: medium,
|
||||
h3: dim,
|
||||
code: medium,
|
||||
code_border: Color::Rgb(200, 200, 200),
|
||||
link: dark,
|
||||
link_url: dim,
|
||||
quote: fg_muted,
|
||||
text: fg,
|
||||
list: fg,
|
||||
},
|
||||
engine: EngineColors {
|
||||
header: dark,
|
||||
header_focused: dark,
|
||||
divider: Color::Rgb(210, 210, 210),
|
||||
scroll_indicator: Color::Rgb(180, 180, 180),
|
||||
label: dim,
|
||||
label_focused: medium,
|
||||
label_dim: light,
|
||||
value: fg,
|
||||
focused: dark,
|
||||
normal: fg,
|
||||
dim: light,
|
||||
path: dim,
|
||||
border_magenta: medium,
|
||||
border_green: medium,
|
||||
border_cyan: medium,
|
||||
separator: Color::Rgb(210, 210, 210),
|
||||
hint_active: dark,
|
||||
hint_inactive: lighter,
|
||||
},
|
||||
dict: DictColors {
|
||||
word_name: dark,
|
||||
word_bg: Color::Rgb(230, 230, 230),
|
||||
alias: fg_muted,
|
||||
stack_sig: medium,
|
||||
description: fg,
|
||||
example: dim,
|
||||
category_focused: dark,
|
||||
category_selected: medium,
|
||||
category_normal: fg,
|
||||
category_dimmed: light,
|
||||
border_focused: dark,
|
||||
border_normal: lighter,
|
||||
header_desc: dim,
|
||||
},
|
||||
title: TitleColors {
|
||||
big_title: dark,
|
||||
author: medium,
|
||||
link: medium,
|
||||
license: dim,
|
||||
prompt: dim,
|
||||
subtitle: fg,
|
||||
},
|
||||
meter: MeterColors {
|
||||
low: dim,
|
||||
mid: medium,
|
||||
high: dark,
|
||||
low_rgb: (140, 140, 140),
|
||||
mid_rgb: (80, 80, 80),
|
||||
high_rgb: (0, 0, 0),
|
||||
},
|
||||
sparkle: SparkleColors {
|
||||
colors: [
|
||||
(0, 0, 0),
|
||||
(60, 60, 60),
|
||||
(100, 100, 100),
|
||||
(40, 40, 40),
|
||||
(80, 80, 80),
|
||||
],
|
||||
},
|
||||
confirm: ConfirmColors {
|
||||
border: dark,
|
||||
button_selected_bg: dark,
|
||||
button_selected_fg: bg,
|
||||
},
|
||||
}
|
||||
}
|
||||
276
crates/ratatui/src/theme/monokai.rs
Normal file
276
crates/ratatui/src/theme/monokai.rs
Normal file
@@ -0,0 +1,276 @@
|
||||
use super::*;
|
||||
use ratatui::style::Color;
|
||||
|
||||
pub fn theme() -> ThemeColors {
|
||||
let bg = Color::Rgb(39, 40, 34);
|
||||
let bg_light = Color::Rgb(53, 54, 47);
|
||||
let bg_lighter = Color::Rgb(70, 71, 62);
|
||||
let fg = Color::Rgb(248, 248, 242);
|
||||
let fg_dim = Color::Rgb(190, 190, 180);
|
||||
let comment = Color::Rgb(117, 113, 94);
|
||||
let pink = Color::Rgb(249, 38, 114);
|
||||
let green = Color::Rgb(166, 226, 46);
|
||||
let yellow = Color::Rgb(230, 219, 116);
|
||||
let blue = Color::Rgb(102, 217, 239);
|
||||
let purple = Color::Rgb(174, 129, 255);
|
||||
let orange = Color::Rgb(253, 151, 31);
|
||||
|
||||
let darker_bg = Color::Rgb(30, 31, 26);
|
||||
|
||||
ThemeColors {
|
||||
ui: UiColors {
|
||||
bg,
|
||||
bg_rgb: (39, 40, 34),
|
||||
text_primary: fg,
|
||||
text_muted: fg_dim,
|
||||
text_dim: comment,
|
||||
border: bg_lighter,
|
||||
header: blue,
|
||||
unfocused: comment,
|
||||
accent: pink,
|
||||
surface: bg_light,
|
||||
},
|
||||
status: StatusColors {
|
||||
playing_bg: Color::Rgb(50, 65, 40),
|
||||
playing_fg: green,
|
||||
stopped_bg: Color::Rgb(70, 40, 55),
|
||||
stopped_fg: pink,
|
||||
fill_on: green,
|
||||
fill_off: comment,
|
||||
fill_bg: bg_light,
|
||||
},
|
||||
selection: SelectionColors {
|
||||
cursor_bg: pink,
|
||||
cursor_fg: bg,
|
||||
selected_bg: Color::Rgb(85, 70, 80),
|
||||
selected_fg: pink,
|
||||
in_range_bg: Color::Rgb(70, 65, 70),
|
||||
in_range_fg: fg,
|
||||
cursor: pink,
|
||||
selected: Color::Rgb(85, 70, 80),
|
||||
in_range: Color::Rgb(70, 65, 70),
|
||||
},
|
||||
tile: TileColors {
|
||||
playing_active_bg: Color::Rgb(90, 65, 45),
|
||||
playing_active_fg: orange,
|
||||
playing_inactive_bg: Color::Rgb(80, 75, 50),
|
||||
playing_inactive_fg: yellow,
|
||||
active_bg: Color::Rgb(55, 75, 70),
|
||||
active_fg: blue,
|
||||
inactive_bg: bg_light,
|
||||
inactive_fg: fg_dim,
|
||||
active_selected_bg: Color::Rgb(85, 65, 80),
|
||||
active_in_range_bg: Color::Rgb(70, 65, 70),
|
||||
link_bright: [
|
||||
(249, 38, 114),
|
||||
(174, 129, 255),
|
||||
(253, 151, 31),
|
||||
(102, 217, 239),
|
||||
(166, 226, 46),
|
||||
],
|
||||
link_dim: [
|
||||
(90, 40, 60),
|
||||
(70, 55, 90),
|
||||
(85, 60, 35),
|
||||
(50, 75, 85),
|
||||
(60, 80, 40),
|
||||
],
|
||||
},
|
||||
header: HeaderColors {
|
||||
tempo_bg: Color::Rgb(75, 50, 65),
|
||||
tempo_fg: pink,
|
||||
bank_bg: Color::Rgb(50, 70, 75),
|
||||
bank_fg: blue,
|
||||
pattern_bg: Color::Rgb(55, 75, 50),
|
||||
pattern_fg: green,
|
||||
stats_bg: bg_light,
|
||||
stats_fg: fg_dim,
|
||||
},
|
||||
modal: ModalColors {
|
||||
border: blue,
|
||||
border_accent: pink,
|
||||
border_warn: orange,
|
||||
border_dim: comment,
|
||||
confirm: orange,
|
||||
rename: purple,
|
||||
input: blue,
|
||||
editor: blue,
|
||||
preview: comment,
|
||||
},
|
||||
flash: FlashColors {
|
||||
error_bg: Color::Rgb(75, 40, 55),
|
||||
error_fg: pink,
|
||||
success_bg: Color::Rgb(50, 70, 45),
|
||||
success_fg: green,
|
||||
info_bg: bg_light,
|
||||
info_fg: fg,
|
||||
event_rgb: (70, 55, 70),
|
||||
},
|
||||
list: ListColors {
|
||||
playing_bg: Color::Rgb(50, 70, 45),
|
||||
playing_fg: green,
|
||||
staged_play_bg: Color::Rgb(70, 55, 80),
|
||||
staged_play_fg: purple,
|
||||
staged_stop_bg: Color::Rgb(80, 45, 60),
|
||||
staged_stop_fg: pink,
|
||||
edit_bg: Color::Rgb(50, 70, 70),
|
||||
edit_fg: blue,
|
||||
hover_bg: bg_lighter,
|
||||
hover_fg: fg,
|
||||
},
|
||||
link_status: LinkStatusColors {
|
||||
disabled: pink,
|
||||
connected: green,
|
||||
listening: yellow,
|
||||
},
|
||||
syntax: SyntaxColors {
|
||||
gap_bg: darker_bg,
|
||||
executed_bg: Color::Rgb(55, 50, 55),
|
||||
selected_bg: Color::Rgb(85, 75, 50),
|
||||
emit: (fg, Color::Rgb(85, 55, 65)),
|
||||
number: (purple, Color::Rgb(60, 50, 75)),
|
||||
string: (yellow, Color::Rgb(70, 65, 45)),
|
||||
comment: (comment, darker_bg),
|
||||
keyword: (pink, Color::Rgb(80, 45, 60)),
|
||||
stack_op: (blue, Color::Rgb(50, 70, 75)),
|
||||
operator: (pink, Color::Rgb(80, 45, 60)),
|
||||
sound: (blue, Color::Rgb(50, 70, 75)),
|
||||
param: (orange, Color::Rgb(80, 60, 40)),
|
||||
context: (orange, Color::Rgb(80, 60, 40)),
|
||||
note: (green, Color::Rgb(55, 75, 45)),
|
||||
interval: (Color::Rgb(180, 235, 80), Color::Rgb(55, 75, 40)),
|
||||
variable: (green, Color::Rgb(55, 75, 45)),
|
||||
vary: (yellow, Color::Rgb(70, 65, 45)),
|
||||
generator: (blue, Color::Rgb(50, 70, 70)),
|
||||
default: (fg_dim, darker_bg),
|
||||
},
|
||||
table: TableColors {
|
||||
row_even: darker_bg,
|
||||
row_odd: bg,
|
||||
},
|
||||
values: ValuesColors {
|
||||
tempo: orange,
|
||||
value: fg_dim,
|
||||
},
|
||||
hint: HintColors {
|
||||
key: orange,
|
||||
text: comment,
|
||||
},
|
||||
view_badge: ViewBadgeColors { bg: fg, fg: bg },
|
||||
nav: NavColors {
|
||||
selected_bg: Color::Rgb(80, 60, 75),
|
||||
selected_fg: fg,
|
||||
unselected_bg: bg_light,
|
||||
unselected_fg: comment,
|
||||
},
|
||||
editor_widget: EditorWidgetColors {
|
||||
cursor_bg: fg,
|
||||
cursor_fg: bg,
|
||||
selection_bg: Color::Rgb(75, 70, 75),
|
||||
completion_bg: bg_light,
|
||||
completion_fg: fg,
|
||||
completion_selected: orange,
|
||||
completion_example: blue,
|
||||
},
|
||||
browser: BrowserColors {
|
||||
directory: blue,
|
||||
project_file: purple,
|
||||
selected: orange,
|
||||
file: fg,
|
||||
focused_border: orange,
|
||||
unfocused_border: comment,
|
||||
root: fg,
|
||||
file_icon: comment,
|
||||
folder_icon: blue,
|
||||
empty_text: comment,
|
||||
},
|
||||
input: InputColors {
|
||||
text: blue,
|
||||
cursor: fg,
|
||||
hint: comment,
|
||||
},
|
||||
search: SearchColors {
|
||||
active: orange,
|
||||
inactive: comment,
|
||||
match_bg: yellow,
|
||||
match_fg: bg,
|
||||
},
|
||||
markdown: MarkdownColors {
|
||||
h1: blue,
|
||||
h2: orange,
|
||||
h3: purple,
|
||||
code: green,
|
||||
code_border: Color::Rgb(85, 85, 75),
|
||||
link: pink,
|
||||
link_url: Color::Rgb(130, 125, 115),
|
||||
quote: comment,
|
||||
text: fg,
|
||||
list: fg,
|
||||
},
|
||||
engine: EngineColors {
|
||||
header: blue,
|
||||
header_focused: yellow,
|
||||
divider: Color::Rgb(80, 80, 72),
|
||||
scroll_indicator: Color::Rgb(95, 95, 88),
|
||||
label: Color::Rgb(150, 145, 135),
|
||||
label_focused: Color::Rgb(180, 175, 165),
|
||||
label_dim: Color::Rgb(120, 115, 105),
|
||||
value: Color::Rgb(210, 205, 195),
|
||||
focused: yellow,
|
||||
normal: fg,
|
||||
dim: Color::Rgb(95, 95, 88),
|
||||
path: Color::Rgb(150, 145, 135),
|
||||
border_magenta: pink,
|
||||
border_green: green,
|
||||
border_cyan: blue,
|
||||
separator: Color::Rgb(80, 80, 72),
|
||||
hint_active: Color::Rgb(220, 200, 100),
|
||||
hint_inactive: Color::Rgb(80, 80, 72),
|
||||
},
|
||||
dict: DictColors {
|
||||
word_name: green,
|
||||
word_bg: Color::Rgb(55, 65, 60),
|
||||
alias: comment,
|
||||
stack_sig: purple,
|
||||
description: fg,
|
||||
example: Color::Rgb(150, 145, 135),
|
||||
category_focused: yellow,
|
||||
category_selected: blue,
|
||||
category_normal: fg,
|
||||
category_dimmed: Color::Rgb(95, 95, 88),
|
||||
border_focused: yellow,
|
||||
border_normal: Color::Rgb(80, 80, 72),
|
||||
header_desc: Color::Rgb(170, 165, 155),
|
||||
},
|
||||
title: TitleColors {
|
||||
big_title: pink,
|
||||
author: blue,
|
||||
link: green,
|
||||
license: orange,
|
||||
prompt: Color::Rgb(170, 165, 155),
|
||||
subtitle: fg,
|
||||
},
|
||||
meter: MeterColors {
|
||||
low: green,
|
||||
mid: yellow,
|
||||
high: pink,
|
||||
low_rgb: (155, 215, 45),
|
||||
mid_rgb: (220, 210, 105),
|
||||
high_rgb: (240, 50, 110),
|
||||
},
|
||||
sparkle: SparkleColors {
|
||||
colors: [
|
||||
(102, 217, 239),
|
||||
(253, 151, 31),
|
||||
(166, 226, 46),
|
||||
(249, 38, 114),
|
||||
(174, 129, 255),
|
||||
],
|
||||
},
|
||||
confirm: ConfirmColors {
|
||||
border: orange,
|
||||
button_selected_bg: orange,
|
||||
button_selected_fg: bg,
|
||||
},
|
||||
}
|
||||
}
|
||||
279
crates/ratatui/src/theme/nord.rs
Normal file
279
crates/ratatui/src/theme/nord.rs
Normal file
@@ -0,0 +1,279 @@
|
||||
use super::*;
|
||||
use ratatui::style::Color;
|
||||
|
||||
pub fn theme() -> ThemeColors {
|
||||
let polar_night0 = Color::Rgb(46, 52, 64);
|
||||
let polar_night1 = Color::Rgb(59, 66, 82);
|
||||
let polar_night2 = Color::Rgb(67, 76, 94);
|
||||
let polar_night3 = Color::Rgb(76, 86, 106);
|
||||
let snow_storm0 = Color::Rgb(216, 222, 233);
|
||||
let snow_storm2 = Color::Rgb(236, 239, 244);
|
||||
let frost0 = Color::Rgb(143, 188, 187);
|
||||
let frost1 = Color::Rgb(136, 192, 208);
|
||||
let frost2 = Color::Rgb(129, 161, 193);
|
||||
let aurora_red = Color::Rgb(191, 97, 106);
|
||||
let aurora_orange = Color::Rgb(208, 135, 112);
|
||||
let aurora_yellow = Color::Rgb(235, 203, 139);
|
||||
let aurora_green = Color::Rgb(163, 190, 140);
|
||||
let aurora_purple = Color::Rgb(180, 142, 173);
|
||||
|
||||
ThemeColors {
|
||||
ui: UiColors {
|
||||
bg: polar_night0,
|
||||
bg_rgb: (46, 52, 64),
|
||||
text_primary: snow_storm2,
|
||||
text_muted: snow_storm0,
|
||||
text_dim: polar_night3,
|
||||
border: polar_night2,
|
||||
header: frost1,
|
||||
unfocused: polar_night3,
|
||||
accent: frost1,
|
||||
surface: polar_night1,
|
||||
},
|
||||
status: StatusColors {
|
||||
playing_bg: Color::Rgb(50, 65, 60),
|
||||
playing_fg: aurora_green,
|
||||
stopped_bg: Color::Rgb(65, 50, 55),
|
||||
stopped_fg: aurora_red,
|
||||
fill_on: aurora_green,
|
||||
fill_off: polar_night3,
|
||||
fill_bg: polar_night1,
|
||||
},
|
||||
selection: SelectionColors {
|
||||
cursor_bg: frost1,
|
||||
cursor_fg: polar_night0,
|
||||
selected_bg: Color::Rgb(70, 85, 105),
|
||||
selected_fg: frost1,
|
||||
in_range_bg: Color::Rgb(60, 70, 90),
|
||||
in_range_fg: snow_storm0,
|
||||
cursor: frost1,
|
||||
selected: Color::Rgb(70, 85, 105),
|
||||
in_range: Color::Rgb(60, 70, 90),
|
||||
},
|
||||
tile: TileColors {
|
||||
playing_active_bg: Color::Rgb(80, 70, 65),
|
||||
playing_active_fg: aurora_orange,
|
||||
playing_inactive_bg: Color::Rgb(75, 70, 55),
|
||||
playing_inactive_fg: aurora_yellow,
|
||||
active_bg: Color::Rgb(50, 65, 65),
|
||||
active_fg: frost0,
|
||||
inactive_bg: polar_night1,
|
||||
inactive_fg: snow_storm0,
|
||||
active_selected_bg: Color::Rgb(75, 75, 95),
|
||||
active_in_range_bg: Color::Rgb(60, 70, 85),
|
||||
link_bright: [
|
||||
(136, 192, 208),
|
||||
(180, 142, 173),
|
||||
(208, 135, 112),
|
||||
(143, 188, 187),
|
||||
(163, 190, 140),
|
||||
],
|
||||
link_dim: [
|
||||
(55, 75, 85),
|
||||
(70, 60, 70),
|
||||
(75, 55, 50),
|
||||
(55, 75, 75),
|
||||
(60, 75, 55),
|
||||
],
|
||||
},
|
||||
header: HeaderColors {
|
||||
tempo_bg: Color::Rgb(65, 55, 70),
|
||||
tempo_fg: aurora_purple,
|
||||
bank_bg: Color::Rgb(45, 60, 70),
|
||||
bank_fg: frost2,
|
||||
pattern_bg: Color::Rgb(50, 65, 65),
|
||||
pattern_fg: frost0,
|
||||
stats_bg: polar_night1,
|
||||
stats_fg: snow_storm0,
|
||||
},
|
||||
modal: ModalColors {
|
||||
border: frost1,
|
||||
border_accent: aurora_purple,
|
||||
border_warn: aurora_orange,
|
||||
border_dim: polar_night3,
|
||||
confirm: aurora_orange,
|
||||
rename: aurora_purple,
|
||||
input: frost2,
|
||||
editor: frost1,
|
||||
preview: polar_night3,
|
||||
},
|
||||
flash: FlashColors {
|
||||
error_bg: Color::Rgb(65, 50, 55),
|
||||
error_fg: aurora_red,
|
||||
success_bg: Color::Rgb(50, 65, 55),
|
||||
success_fg: aurora_green,
|
||||
info_bg: polar_night1,
|
||||
info_fg: snow_storm2,
|
||||
event_rgb: (60, 55, 75),
|
||||
},
|
||||
list: ListColors {
|
||||
playing_bg: Color::Rgb(50, 65, 55),
|
||||
playing_fg: aurora_green,
|
||||
staged_play_bg: Color::Rgb(65, 55, 70),
|
||||
staged_play_fg: aurora_purple,
|
||||
staged_stop_bg: Color::Rgb(70, 55, 60),
|
||||
staged_stop_fg: aurora_red,
|
||||
edit_bg: Color::Rgb(50, 65, 65),
|
||||
edit_fg: frost0,
|
||||
hover_bg: polar_night2,
|
||||
hover_fg: snow_storm2,
|
||||
},
|
||||
link_status: LinkStatusColors {
|
||||
disabled: aurora_red,
|
||||
connected: aurora_green,
|
||||
listening: aurora_yellow,
|
||||
},
|
||||
syntax: SyntaxColors {
|
||||
gap_bg: polar_night1,
|
||||
executed_bg: Color::Rgb(55, 55, 70),
|
||||
selected_bg: Color::Rgb(80, 70, 55),
|
||||
emit: (snow_storm2, Color::Rgb(75, 55, 60)),
|
||||
number: (aurora_orange, Color::Rgb(65, 55, 50)),
|
||||
string: (aurora_green, Color::Rgb(50, 60, 50)),
|
||||
comment: (polar_night3, polar_night0),
|
||||
keyword: (aurora_purple, Color::Rgb(60, 50, 65)),
|
||||
stack_op: (frost2, Color::Rgb(45, 55, 70)),
|
||||
operator: (aurora_yellow, Color::Rgb(65, 60, 45)),
|
||||
sound: (frost0, Color::Rgb(45, 60, 60)),
|
||||
param: (frost1, Color::Rgb(50, 60, 70)),
|
||||
context: (aurora_orange, Color::Rgb(65, 55, 50)),
|
||||
note: (aurora_green, Color::Rgb(50, 60, 50)),
|
||||
interval: (Color::Rgb(170, 200, 150), Color::Rgb(50, 60, 45)),
|
||||
variable: (aurora_purple, Color::Rgb(60, 50, 60)),
|
||||
vary: (aurora_yellow, Color::Rgb(65, 60, 45)),
|
||||
generator: (frost0, Color::Rgb(45, 60, 55)),
|
||||
default: (snow_storm0, polar_night1),
|
||||
},
|
||||
table: TableColors {
|
||||
row_even: polar_night1,
|
||||
row_odd: polar_night0,
|
||||
},
|
||||
values: ValuesColors {
|
||||
tempo: aurora_orange,
|
||||
value: snow_storm0,
|
||||
},
|
||||
hint: HintColors {
|
||||
key: aurora_orange,
|
||||
text: polar_night3,
|
||||
},
|
||||
view_badge: ViewBadgeColors {
|
||||
bg: snow_storm2,
|
||||
fg: polar_night0,
|
||||
},
|
||||
nav: NavColors {
|
||||
selected_bg: Color::Rgb(65, 75, 95),
|
||||
selected_fg: snow_storm2,
|
||||
unselected_bg: polar_night1,
|
||||
unselected_fg: polar_night3,
|
||||
},
|
||||
editor_widget: EditorWidgetColors {
|
||||
cursor_bg: snow_storm2,
|
||||
cursor_fg: polar_night0,
|
||||
selection_bg: Color::Rgb(60, 75, 100),
|
||||
completion_bg: polar_night1,
|
||||
completion_fg: snow_storm2,
|
||||
completion_selected: aurora_orange,
|
||||
completion_example: frost0,
|
||||
},
|
||||
browser: BrowserColors {
|
||||
directory: frost2,
|
||||
project_file: aurora_purple,
|
||||
selected: aurora_orange,
|
||||
file: snow_storm2,
|
||||
focused_border: aurora_orange,
|
||||
unfocused_border: polar_night3,
|
||||
root: snow_storm2,
|
||||
file_icon: polar_night3,
|
||||
folder_icon: frost2,
|
||||
empty_text: polar_night3,
|
||||
},
|
||||
input: InputColors {
|
||||
text: frost2,
|
||||
cursor: snow_storm2,
|
||||
hint: polar_night3,
|
||||
},
|
||||
search: SearchColors {
|
||||
active: aurora_orange,
|
||||
inactive: polar_night3,
|
||||
match_bg: aurora_yellow,
|
||||
match_fg: polar_night0,
|
||||
},
|
||||
markdown: MarkdownColors {
|
||||
h1: frost2,
|
||||
h2: aurora_orange,
|
||||
h3: aurora_purple,
|
||||
code: aurora_green,
|
||||
code_border: Color::Rgb(75, 85, 100),
|
||||
link: frost0,
|
||||
link_url: Color::Rgb(100, 110, 125),
|
||||
quote: polar_night3,
|
||||
text: snow_storm2,
|
||||
list: snow_storm2,
|
||||
},
|
||||
engine: EngineColors {
|
||||
header: frost1,
|
||||
header_focused: aurora_yellow,
|
||||
divider: Color::Rgb(70, 80, 95),
|
||||
scroll_indicator: Color::Rgb(85, 95, 110),
|
||||
label: Color::Rgb(130, 140, 155),
|
||||
label_focused: Color::Rgb(160, 170, 185),
|
||||
label_dim: Color::Rgb(100, 110, 125),
|
||||
value: Color::Rgb(190, 200, 215),
|
||||
focused: aurora_yellow,
|
||||
normal: snow_storm2,
|
||||
dim: Color::Rgb(85, 95, 110),
|
||||
path: Color::Rgb(130, 140, 155),
|
||||
border_magenta: aurora_purple,
|
||||
border_green: aurora_green,
|
||||
border_cyan: frost2,
|
||||
separator: Color::Rgb(70, 80, 95),
|
||||
hint_active: Color::Rgb(200, 180, 100),
|
||||
hint_inactive: Color::Rgb(70, 80, 95),
|
||||
},
|
||||
dict: DictColors {
|
||||
word_name: aurora_green,
|
||||
word_bg: Color::Rgb(50, 60, 75),
|
||||
alias: polar_night3,
|
||||
stack_sig: aurora_purple,
|
||||
description: snow_storm2,
|
||||
example: Color::Rgb(130, 140, 155),
|
||||
category_focused: aurora_yellow,
|
||||
category_selected: frost2,
|
||||
category_normal: snow_storm2,
|
||||
category_dimmed: Color::Rgb(85, 95, 110),
|
||||
border_focused: aurora_yellow,
|
||||
border_normal: Color::Rgb(70, 80, 95),
|
||||
header_desc: Color::Rgb(150, 160, 175),
|
||||
},
|
||||
title: TitleColors {
|
||||
big_title: frost1,
|
||||
author: frost2,
|
||||
link: frost0,
|
||||
license: aurora_orange,
|
||||
prompt: Color::Rgb(150, 160, 175),
|
||||
subtitle: snow_storm2,
|
||||
},
|
||||
meter: MeterColors {
|
||||
low: aurora_green,
|
||||
mid: aurora_yellow,
|
||||
high: aurora_red,
|
||||
low_rgb: (140, 180, 130),
|
||||
mid_rgb: (220, 190, 120),
|
||||
high_rgb: (180, 90, 100),
|
||||
},
|
||||
sparkle: SparkleColors {
|
||||
colors: [
|
||||
(136, 192, 208),
|
||||
(208, 135, 112),
|
||||
(163, 190, 140),
|
||||
(180, 142, 173),
|
||||
(235, 203, 139),
|
||||
],
|
||||
},
|
||||
confirm: ConfirmColors {
|
||||
border: aurora_orange,
|
||||
button_selected_bg: aurora_orange,
|
||||
button_selected_fg: polar_night0,
|
||||
},
|
||||
}
|
||||
}
|
||||
277
crates/ratatui/src/theme/pitch_black.rs
Normal file
277
crates/ratatui/src/theme/pitch_black.rs
Normal file
@@ -0,0 +1,277 @@
|
||||
use super::*;
|
||||
use ratatui::style::Color;
|
||||
|
||||
pub fn theme() -> ThemeColors {
|
||||
let bg = Color::Rgb(0, 0, 0);
|
||||
let surface = Color::Rgb(10, 10, 10);
|
||||
let surface2 = Color::Rgb(21, 21, 21);
|
||||
let border = Color::Rgb(40, 40, 40);
|
||||
let fg = Color::Rgb(230, 230, 230);
|
||||
let fg_dim = Color::Rgb(160, 160, 160);
|
||||
let fg_muted = Color::Rgb(100, 100, 100);
|
||||
|
||||
let red = Color::Rgb(255, 80, 80);
|
||||
let green = Color::Rgb(80, 255, 120);
|
||||
let yellow = Color::Rgb(255, 230, 80);
|
||||
let blue = Color::Rgb(80, 180, 255);
|
||||
let purple = Color::Rgb(200, 120, 255);
|
||||
let cyan = Color::Rgb(80, 230, 230);
|
||||
let orange = Color::Rgb(255, 160, 60);
|
||||
|
||||
ThemeColors {
|
||||
ui: UiColors {
|
||||
bg,
|
||||
bg_rgb: (0, 0, 0),
|
||||
text_primary: fg,
|
||||
text_muted: fg_dim,
|
||||
text_dim: fg_muted,
|
||||
border,
|
||||
header: blue,
|
||||
unfocused: fg_muted,
|
||||
accent: cyan,
|
||||
surface,
|
||||
},
|
||||
status: StatusColors {
|
||||
playing_bg: Color::Rgb(15, 35, 20),
|
||||
playing_fg: green,
|
||||
stopped_bg: Color::Rgb(40, 15, 20),
|
||||
stopped_fg: red,
|
||||
fill_on: green,
|
||||
fill_off: fg_muted,
|
||||
fill_bg: surface,
|
||||
},
|
||||
selection: SelectionColors {
|
||||
cursor_bg: cyan,
|
||||
cursor_fg: bg,
|
||||
selected_bg: Color::Rgb(40, 50, 60),
|
||||
selected_fg: cyan,
|
||||
in_range_bg: Color::Rgb(25, 35, 45),
|
||||
in_range_fg: fg,
|
||||
cursor: cyan,
|
||||
selected: Color::Rgb(40, 50, 60),
|
||||
in_range: Color::Rgb(25, 35, 45),
|
||||
},
|
||||
tile: TileColors {
|
||||
playing_active_bg: Color::Rgb(50, 35, 20),
|
||||
playing_active_fg: orange,
|
||||
playing_inactive_bg: Color::Rgb(45, 40, 15),
|
||||
playing_inactive_fg: yellow,
|
||||
active_bg: Color::Rgb(15, 40, 40),
|
||||
active_fg: cyan,
|
||||
inactive_bg: surface,
|
||||
inactive_fg: fg_dim,
|
||||
active_selected_bg: Color::Rgb(45, 40, 55),
|
||||
active_in_range_bg: Color::Rgb(30, 35, 45),
|
||||
link_bright: [
|
||||
(80, 230, 230),
|
||||
(200, 120, 255),
|
||||
(255, 160, 60),
|
||||
(80, 180, 255),
|
||||
(80, 255, 120),
|
||||
],
|
||||
link_dim: [
|
||||
(25, 60, 60),
|
||||
(50, 35, 65),
|
||||
(60, 45, 20),
|
||||
(25, 50, 70),
|
||||
(25, 65, 35),
|
||||
],
|
||||
},
|
||||
header: HeaderColors {
|
||||
tempo_bg: Color::Rgb(50, 35, 55),
|
||||
tempo_fg: purple,
|
||||
bank_bg: Color::Rgb(20, 45, 60),
|
||||
bank_fg: blue,
|
||||
pattern_bg: Color::Rgb(20, 55, 50),
|
||||
pattern_fg: cyan,
|
||||
stats_bg: surface,
|
||||
stats_fg: fg_dim,
|
||||
},
|
||||
modal: ModalColors {
|
||||
border: cyan,
|
||||
border_accent: purple,
|
||||
border_warn: orange,
|
||||
border_dim: fg_muted,
|
||||
confirm: orange,
|
||||
rename: purple,
|
||||
input: blue,
|
||||
editor: cyan,
|
||||
preview: fg_muted,
|
||||
},
|
||||
flash: FlashColors {
|
||||
error_bg: Color::Rgb(50, 15, 20),
|
||||
error_fg: red,
|
||||
success_bg: Color::Rgb(15, 45, 25),
|
||||
success_fg: green,
|
||||
info_bg: surface,
|
||||
info_fg: fg,
|
||||
event_rgb: (40, 30, 50),
|
||||
},
|
||||
list: ListColors {
|
||||
playing_bg: Color::Rgb(15, 45, 25),
|
||||
playing_fg: green,
|
||||
staged_play_bg: Color::Rgb(45, 30, 55),
|
||||
staged_play_fg: purple,
|
||||
staged_stop_bg: Color::Rgb(55, 25, 30),
|
||||
staged_stop_fg: red,
|
||||
edit_bg: Color::Rgb(15, 45, 45),
|
||||
edit_fg: cyan,
|
||||
hover_bg: surface2,
|
||||
hover_fg: fg,
|
||||
},
|
||||
link_status: LinkStatusColors {
|
||||
disabled: red,
|
||||
connected: green,
|
||||
listening: yellow,
|
||||
},
|
||||
syntax: SyntaxColors {
|
||||
gap_bg: bg,
|
||||
executed_bg: Color::Rgb(25, 25, 35),
|
||||
selected_bg: Color::Rgb(55, 45, 25),
|
||||
emit: (fg, Color::Rgb(50, 30, 35)),
|
||||
number: (orange, Color::Rgb(50, 35, 20)),
|
||||
string: (green, Color::Rgb(20, 45, 25)),
|
||||
comment: (fg_muted, bg),
|
||||
keyword: (purple, Color::Rgb(40, 25, 50)),
|
||||
stack_op: (blue, Color::Rgb(20, 40, 55)),
|
||||
operator: (yellow, Color::Rgb(50, 45, 20)),
|
||||
sound: (cyan, Color::Rgb(20, 45, 45)),
|
||||
param: (purple, Color::Rgb(40, 25, 50)),
|
||||
context: (orange, Color::Rgb(50, 35, 20)),
|
||||
note: (green, Color::Rgb(20, 45, 25)),
|
||||
interval: (Color::Rgb(130, 255, 150), Color::Rgb(25, 55, 35)),
|
||||
variable: (purple, Color::Rgb(40, 25, 50)),
|
||||
vary: (yellow, Color::Rgb(50, 45, 20)),
|
||||
generator: (cyan, Color::Rgb(20, 45, 40)),
|
||||
default: (fg_dim, bg),
|
||||
},
|
||||
table: TableColors {
|
||||
row_even: bg,
|
||||
row_odd: surface,
|
||||
},
|
||||
values: ValuesColors {
|
||||
tempo: orange,
|
||||
value: fg_dim,
|
||||
},
|
||||
hint: HintColors {
|
||||
key: orange,
|
||||
text: fg_muted,
|
||||
},
|
||||
view_badge: ViewBadgeColors { bg: fg, fg: bg },
|
||||
nav: NavColors {
|
||||
selected_bg: Color::Rgb(40, 45, 55),
|
||||
selected_fg: fg,
|
||||
unselected_bg: surface,
|
||||
unselected_fg: fg_muted,
|
||||
},
|
||||
editor_widget: EditorWidgetColors {
|
||||
cursor_bg: fg,
|
||||
cursor_fg: bg,
|
||||
selection_bg: Color::Rgb(40, 50, 65),
|
||||
completion_bg: surface,
|
||||
completion_fg: fg,
|
||||
completion_selected: orange,
|
||||
completion_example: cyan,
|
||||
},
|
||||
browser: BrowserColors {
|
||||
directory: blue,
|
||||
project_file: purple,
|
||||
selected: orange,
|
||||
file: fg,
|
||||
focused_border: orange,
|
||||
unfocused_border: fg_muted,
|
||||
root: fg,
|
||||
file_icon: fg_muted,
|
||||
folder_icon: blue,
|
||||
empty_text: fg_muted,
|
||||
},
|
||||
input: InputColors {
|
||||
text: blue,
|
||||
cursor: fg,
|
||||
hint: fg_muted,
|
||||
},
|
||||
search: SearchColors {
|
||||
active: orange,
|
||||
inactive: fg_muted,
|
||||
match_bg: yellow,
|
||||
match_fg: bg,
|
||||
},
|
||||
markdown: MarkdownColors {
|
||||
h1: blue,
|
||||
h2: orange,
|
||||
h3: purple,
|
||||
code: green,
|
||||
code_border: Color::Rgb(50, 50, 50),
|
||||
link: cyan,
|
||||
link_url: Color::Rgb(90, 90, 90),
|
||||
quote: fg_muted,
|
||||
text: fg,
|
||||
list: fg,
|
||||
},
|
||||
engine: EngineColors {
|
||||
header: blue,
|
||||
header_focused: yellow,
|
||||
divider: Color::Rgb(45, 45, 45),
|
||||
scroll_indicator: Color::Rgb(60, 60, 60),
|
||||
label: Color::Rgb(130, 130, 130),
|
||||
label_focused: Color::Rgb(170, 170, 170),
|
||||
label_dim: Color::Rgb(90, 90, 90),
|
||||
value: Color::Rgb(200, 200, 200),
|
||||
focused: yellow,
|
||||
normal: fg,
|
||||
dim: Color::Rgb(60, 60, 60),
|
||||
path: Color::Rgb(130, 130, 130),
|
||||
border_magenta: purple,
|
||||
border_green: green,
|
||||
border_cyan: cyan,
|
||||
separator: Color::Rgb(45, 45, 45),
|
||||
hint_active: Color::Rgb(220, 200, 80),
|
||||
hint_inactive: Color::Rgb(45, 45, 45),
|
||||
},
|
||||
dict: DictColors {
|
||||
word_name: green,
|
||||
word_bg: Color::Rgb(20, 30, 35),
|
||||
alias: fg_muted,
|
||||
stack_sig: purple,
|
||||
description: fg,
|
||||
example: Color::Rgb(130, 130, 130),
|
||||
category_focused: yellow,
|
||||
category_selected: blue,
|
||||
category_normal: fg,
|
||||
category_dimmed: Color::Rgb(60, 60, 60),
|
||||
border_focused: yellow,
|
||||
border_normal: Color::Rgb(45, 45, 45),
|
||||
header_desc: Color::Rgb(150, 150, 150),
|
||||
},
|
||||
title: TitleColors {
|
||||
big_title: cyan,
|
||||
author: blue,
|
||||
link: green,
|
||||
license: orange,
|
||||
prompt: Color::Rgb(150, 150, 150),
|
||||
subtitle: fg,
|
||||
},
|
||||
meter: MeterColors {
|
||||
low: green,
|
||||
mid: yellow,
|
||||
high: red,
|
||||
low_rgb: (70, 240, 110),
|
||||
mid_rgb: (245, 220, 75),
|
||||
high_rgb: (245, 75, 75),
|
||||
},
|
||||
sparkle: SparkleColors {
|
||||
colors: [
|
||||
(80, 230, 230),
|
||||
(255, 160, 60),
|
||||
(80, 255, 120),
|
||||
(200, 120, 255),
|
||||
(80, 180, 255),
|
||||
],
|
||||
},
|
||||
confirm: ConfirmColors {
|
||||
border: orange,
|
||||
button_selected_bg: orange,
|
||||
button_selected_fg: bg,
|
||||
},
|
||||
}
|
||||
}
|
||||
277
crates/ratatui/src/theme/rose_pine.rs
Normal file
277
crates/ratatui/src/theme/rose_pine.rs
Normal file
@@ -0,0 +1,277 @@
|
||||
use super::*;
|
||||
use ratatui::style::Color;
|
||||
|
||||
pub fn theme() -> ThemeColors {
|
||||
let bg = Color::Rgb(25, 23, 36);
|
||||
let bg_light = Color::Rgb(33, 32, 46);
|
||||
let bg_lighter = Color::Rgb(42, 39, 63);
|
||||
let fg = Color::Rgb(224, 222, 244);
|
||||
let fg_dim = Color::Rgb(144, 140, 170);
|
||||
let muted = Color::Rgb(110, 106, 134);
|
||||
let rose = Color::Rgb(235, 111, 146);
|
||||
let gold = Color::Rgb(246, 193, 119);
|
||||
let foam = Color::Rgb(156, 207, 216);
|
||||
let iris = Color::Rgb(196, 167, 231);
|
||||
let pine = Color::Rgb(49, 116, 143);
|
||||
let subtle = Color::Rgb(235, 188, 186);
|
||||
let love = Color::Rgb(235, 111, 146);
|
||||
|
||||
let darker_bg = Color::Rgb(21, 19, 30);
|
||||
|
||||
ThemeColors {
|
||||
ui: UiColors {
|
||||
bg,
|
||||
bg_rgb: (25, 23, 36),
|
||||
text_primary: fg,
|
||||
text_muted: fg_dim,
|
||||
text_dim: muted,
|
||||
border: bg_lighter,
|
||||
header: foam,
|
||||
unfocused: muted,
|
||||
accent: rose,
|
||||
surface: bg_light,
|
||||
},
|
||||
status: StatusColors {
|
||||
playing_bg: Color::Rgb(35, 50, 55),
|
||||
playing_fg: foam,
|
||||
stopped_bg: Color::Rgb(55, 40, 50),
|
||||
stopped_fg: love,
|
||||
fill_on: foam,
|
||||
fill_off: muted,
|
||||
fill_bg: bg_light,
|
||||
},
|
||||
selection: SelectionColors {
|
||||
cursor_bg: rose,
|
||||
cursor_fg: bg,
|
||||
selected_bg: Color::Rgb(60, 50, 70),
|
||||
selected_fg: rose,
|
||||
in_range_bg: Color::Rgb(50, 45, 60),
|
||||
in_range_fg: fg,
|
||||
cursor: rose,
|
||||
selected: Color::Rgb(60, 50, 70),
|
||||
in_range: Color::Rgb(50, 45, 60),
|
||||
},
|
||||
tile: TileColors {
|
||||
playing_active_bg: Color::Rgb(65, 55, 50),
|
||||
playing_active_fg: gold,
|
||||
playing_inactive_bg: Color::Rgb(55, 55, 55),
|
||||
playing_inactive_fg: subtle,
|
||||
active_bg: Color::Rgb(35, 50, 60),
|
||||
active_fg: foam,
|
||||
inactive_bg: bg_light,
|
||||
inactive_fg: fg_dim,
|
||||
active_selected_bg: Color::Rgb(60, 50, 70),
|
||||
active_in_range_bg: Color::Rgb(50, 45, 60),
|
||||
link_bright: [
|
||||
(235, 111, 146),
|
||||
(196, 167, 231),
|
||||
(246, 193, 119),
|
||||
(156, 207, 216),
|
||||
(49, 116, 143),
|
||||
],
|
||||
link_dim: [
|
||||
(75, 45, 55),
|
||||
(60, 50, 75),
|
||||
(75, 60, 45),
|
||||
(50, 65, 70),
|
||||
(30, 50, 55),
|
||||
],
|
||||
},
|
||||
header: HeaderColors {
|
||||
tempo_bg: Color::Rgb(60, 45, 60),
|
||||
tempo_fg: iris,
|
||||
bank_bg: Color::Rgb(35, 50, 60),
|
||||
bank_fg: foam,
|
||||
pattern_bg: Color::Rgb(35, 55, 60),
|
||||
pattern_fg: pine,
|
||||
stats_bg: bg_light,
|
||||
stats_fg: fg_dim,
|
||||
},
|
||||
modal: ModalColors {
|
||||
border: foam,
|
||||
border_accent: rose,
|
||||
border_warn: gold,
|
||||
border_dim: muted,
|
||||
confirm: gold,
|
||||
rename: iris,
|
||||
input: foam,
|
||||
editor: foam,
|
||||
preview: muted,
|
||||
},
|
||||
flash: FlashColors {
|
||||
error_bg: Color::Rgb(60, 40, 50),
|
||||
error_fg: love,
|
||||
success_bg: Color::Rgb(35, 55, 55),
|
||||
success_fg: foam,
|
||||
info_bg: bg_light,
|
||||
info_fg: fg,
|
||||
event_rgb: (50, 45, 60),
|
||||
},
|
||||
list: ListColors {
|
||||
playing_bg: Color::Rgb(35, 55, 55),
|
||||
playing_fg: foam,
|
||||
staged_play_bg: Color::Rgb(55, 50, 70),
|
||||
staged_play_fg: iris,
|
||||
staged_stop_bg: Color::Rgb(60, 45, 55),
|
||||
staged_stop_fg: love,
|
||||
edit_bg: Color::Rgb(35, 50, 60),
|
||||
edit_fg: foam,
|
||||
hover_bg: bg_lighter,
|
||||
hover_fg: fg,
|
||||
},
|
||||
link_status: LinkStatusColors {
|
||||
disabled: love,
|
||||
connected: foam,
|
||||
listening: gold,
|
||||
},
|
||||
syntax: SyntaxColors {
|
||||
gap_bg: darker_bg,
|
||||
executed_bg: Color::Rgb(40, 40, 55),
|
||||
selected_bg: Color::Rgb(65, 55, 50),
|
||||
emit: (fg, Color::Rgb(60, 45, 60)),
|
||||
number: (iris, Color::Rgb(55, 50, 70)),
|
||||
string: (gold, Color::Rgb(65, 55, 45)),
|
||||
comment: (muted, darker_bg),
|
||||
keyword: (rose, Color::Rgb(60, 45, 55)),
|
||||
stack_op: (foam, Color::Rgb(40, 55, 60)),
|
||||
operator: (love, Color::Rgb(60, 45, 55)),
|
||||
sound: (foam, Color::Rgb(40, 55, 60)),
|
||||
param: (gold, Color::Rgb(65, 55, 45)),
|
||||
context: (gold, Color::Rgb(65, 55, 45)),
|
||||
note: (pine, Color::Rgb(35, 50, 55)),
|
||||
interval: (Color::Rgb(100, 160, 180), Color::Rgb(35, 55, 60)),
|
||||
variable: (pine, Color::Rgb(35, 50, 55)),
|
||||
vary: (subtle, Color::Rgb(60, 55, 55)),
|
||||
generator: (foam, Color::Rgb(40, 55, 60)),
|
||||
default: (fg_dim, darker_bg),
|
||||
},
|
||||
table: TableColors {
|
||||
row_even: darker_bg,
|
||||
row_odd: bg,
|
||||
},
|
||||
values: ValuesColors {
|
||||
tempo: gold,
|
||||
value: fg_dim,
|
||||
},
|
||||
hint: HintColors {
|
||||
key: gold,
|
||||
text: muted,
|
||||
},
|
||||
view_badge: ViewBadgeColors { bg: fg, fg: bg },
|
||||
nav: NavColors {
|
||||
selected_bg: Color::Rgb(60, 50, 70),
|
||||
selected_fg: fg,
|
||||
unselected_bg: bg_light,
|
||||
unselected_fg: muted,
|
||||
},
|
||||
editor_widget: EditorWidgetColors {
|
||||
cursor_bg: fg,
|
||||
cursor_fg: bg,
|
||||
selection_bg: Color::Rgb(55, 50, 70),
|
||||
completion_bg: bg_light,
|
||||
completion_fg: fg,
|
||||
completion_selected: gold,
|
||||
completion_example: foam,
|
||||
},
|
||||
browser: BrowserColors {
|
||||
directory: foam,
|
||||
project_file: iris,
|
||||
selected: gold,
|
||||
file: fg,
|
||||
focused_border: gold,
|
||||
unfocused_border: muted,
|
||||
root: fg,
|
||||
file_icon: muted,
|
||||
folder_icon: foam,
|
||||
empty_text: muted,
|
||||
},
|
||||
input: InputColors {
|
||||
text: foam,
|
||||
cursor: fg,
|
||||
hint: muted,
|
||||
},
|
||||
search: SearchColors {
|
||||
active: gold,
|
||||
inactive: muted,
|
||||
match_bg: gold,
|
||||
match_fg: bg,
|
||||
},
|
||||
markdown: MarkdownColors {
|
||||
h1: foam,
|
||||
h2: gold,
|
||||
h3: iris,
|
||||
code: pine,
|
||||
code_border: Color::Rgb(60, 55, 75),
|
||||
link: rose,
|
||||
link_url: Color::Rgb(100, 95, 120),
|
||||
quote: muted,
|
||||
text: fg,
|
||||
list: fg,
|
||||
},
|
||||
engine: EngineColors {
|
||||
header: foam,
|
||||
header_focused: gold,
|
||||
divider: Color::Rgb(55, 52, 70),
|
||||
scroll_indicator: Color::Rgb(70, 65, 90),
|
||||
label: Color::Rgb(130, 125, 155),
|
||||
label_focused: Color::Rgb(160, 155, 185),
|
||||
label_dim: Color::Rgb(100, 95, 125),
|
||||
value: Color::Rgb(200, 195, 220),
|
||||
focused: gold,
|
||||
normal: fg,
|
||||
dim: Color::Rgb(70, 65, 90),
|
||||
path: Color::Rgb(130, 125, 155),
|
||||
border_magenta: iris,
|
||||
border_green: foam,
|
||||
border_cyan: pine,
|
||||
separator: Color::Rgb(55, 52, 70),
|
||||
hint_active: Color::Rgb(230, 180, 110),
|
||||
hint_inactive: Color::Rgb(55, 52, 70),
|
||||
},
|
||||
dict: DictColors {
|
||||
word_name: pine,
|
||||
word_bg: Color::Rgb(40, 50, 55),
|
||||
alias: muted,
|
||||
stack_sig: iris,
|
||||
description: fg,
|
||||
example: Color::Rgb(130, 125, 155),
|
||||
category_focused: gold,
|
||||
category_selected: foam,
|
||||
category_normal: fg,
|
||||
category_dimmed: Color::Rgb(70, 65, 90),
|
||||
border_focused: gold,
|
||||
border_normal: Color::Rgb(55, 52, 70),
|
||||
header_desc: Color::Rgb(150, 145, 175),
|
||||
},
|
||||
title: TitleColors {
|
||||
big_title: rose,
|
||||
author: foam,
|
||||
link: pine,
|
||||
license: gold,
|
||||
prompt: Color::Rgb(150, 145, 175),
|
||||
subtitle: fg,
|
||||
},
|
||||
meter: MeterColors {
|
||||
low: foam,
|
||||
mid: gold,
|
||||
high: love,
|
||||
low_rgb: (156, 207, 216),
|
||||
mid_rgb: (246, 193, 119),
|
||||
high_rgb: (235, 111, 146),
|
||||
},
|
||||
sparkle: SparkleColors {
|
||||
colors: [
|
||||
(156, 207, 216),
|
||||
(246, 193, 119),
|
||||
(49, 116, 143),
|
||||
(235, 111, 146),
|
||||
(196, 167, 231),
|
||||
],
|
||||
},
|
||||
confirm: ConfirmColors {
|
||||
border: gold,
|
||||
button_selected_bg: gold,
|
||||
button_selected_fg: bg,
|
||||
},
|
||||
}
|
||||
}
|
||||
277
crates/ratatui/src/theme/tokyo_night.rs
Normal file
277
crates/ratatui/src/theme/tokyo_night.rs
Normal file
@@ -0,0 +1,277 @@
|
||||
use super::*;
|
||||
use ratatui::style::Color;
|
||||
|
||||
pub fn theme() -> ThemeColors {
|
||||
let bg = Color::Rgb(26, 27, 38);
|
||||
let bg_light = Color::Rgb(36, 40, 59);
|
||||
let bg_lighter = Color::Rgb(52, 59, 88);
|
||||
let fg = Color::Rgb(169, 177, 214);
|
||||
let fg_dim = Color::Rgb(130, 140, 180);
|
||||
let comment = Color::Rgb(86, 95, 137);
|
||||
let blue = Color::Rgb(122, 162, 247);
|
||||
let purple = Color::Rgb(187, 154, 247);
|
||||
let green = Color::Rgb(158, 206, 106);
|
||||
let red = Color::Rgb(247, 118, 142);
|
||||
let orange = Color::Rgb(224, 175, 104);
|
||||
let cyan = Color::Rgb(125, 207, 255);
|
||||
let yellow = Color::Rgb(224, 175, 104);
|
||||
|
||||
let darker_bg = Color::Rgb(22, 23, 32);
|
||||
|
||||
ThemeColors {
|
||||
ui: UiColors {
|
||||
bg,
|
||||
bg_rgb: (26, 27, 38),
|
||||
text_primary: fg,
|
||||
text_muted: fg_dim,
|
||||
text_dim: comment,
|
||||
border: bg_lighter,
|
||||
header: blue,
|
||||
unfocused: comment,
|
||||
accent: purple,
|
||||
surface: bg_light,
|
||||
},
|
||||
status: StatusColors {
|
||||
playing_bg: Color::Rgb(45, 60, 50),
|
||||
playing_fg: green,
|
||||
stopped_bg: Color::Rgb(60, 40, 50),
|
||||
stopped_fg: red,
|
||||
fill_on: green,
|
||||
fill_off: comment,
|
||||
fill_bg: bg_light,
|
||||
},
|
||||
selection: SelectionColors {
|
||||
cursor_bg: purple,
|
||||
cursor_fg: bg,
|
||||
selected_bg: Color::Rgb(70, 60, 90),
|
||||
selected_fg: purple,
|
||||
in_range_bg: Color::Rgb(55, 55, 75),
|
||||
in_range_fg: fg,
|
||||
cursor: purple,
|
||||
selected: Color::Rgb(70, 60, 90),
|
||||
in_range: Color::Rgb(55, 55, 75),
|
||||
},
|
||||
tile: TileColors {
|
||||
playing_active_bg: Color::Rgb(70, 60, 45),
|
||||
playing_active_fg: orange,
|
||||
playing_inactive_bg: Color::Rgb(60, 60, 50),
|
||||
playing_inactive_fg: yellow,
|
||||
active_bg: Color::Rgb(45, 60, 75),
|
||||
active_fg: blue,
|
||||
inactive_bg: bg_light,
|
||||
inactive_fg: fg_dim,
|
||||
active_selected_bg: Color::Rgb(70, 55, 85),
|
||||
active_in_range_bg: Color::Rgb(55, 55, 75),
|
||||
link_bright: [
|
||||
(247, 118, 142),
|
||||
(187, 154, 247),
|
||||
(224, 175, 104),
|
||||
(125, 207, 255),
|
||||
(158, 206, 106),
|
||||
],
|
||||
link_dim: [
|
||||
(80, 45, 55),
|
||||
(65, 55, 85),
|
||||
(75, 60, 40),
|
||||
(45, 70, 85),
|
||||
(55, 70, 45),
|
||||
],
|
||||
},
|
||||
header: HeaderColors {
|
||||
tempo_bg: Color::Rgb(65, 50, 70),
|
||||
tempo_fg: purple,
|
||||
bank_bg: Color::Rgb(45, 55, 75),
|
||||
bank_fg: blue,
|
||||
pattern_bg: Color::Rgb(50, 65, 50),
|
||||
pattern_fg: green,
|
||||
stats_bg: bg_light,
|
||||
stats_fg: fg_dim,
|
||||
},
|
||||
modal: ModalColors {
|
||||
border: blue,
|
||||
border_accent: purple,
|
||||
border_warn: orange,
|
||||
border_dim: comment,
|
||||
confirm: orange,
|
||||
rename: purple,
|
||||
input: blue,
|
||||
editor: blue,
|
||||
preview: comment,
|
||||
},
|
||||
flash: FlashColors {
|
||||
error_bg: Color::Rgb(65, 40, 50),
|
||||
error_fg: red,
|
||||
success_bg: Color::Rgb(45, 60, 45),
|
||||
success_fg: green,
|
||||
info_bg: bg_light,
|
||||
info_fg: fg,
|
||||
event_rgb: (55, 50, 70),
|
||||
},
|
||||
list: ListColors {
|
||||
playing_bg: Color::Rgb(45, 60, 45),
|
||||
playing_fg: green,
|
||||
staged_play_bg: Color::Rgb(60, 50, 75),
|
||||
staged_play_fg: purple,
|
||||
staged_stop_bg: Color::Rgb(70, 45, 55),
|
||||
staged_stop_fg: red,
|
||||
edit_bg: Color::Rgb(45, 55, 70),
|
||||
edit_fg: blue,
|
||||
hover_bg: bg_lighter,
|
||||
hover_fg: fg,
|
||||
},
|
||||
link_status: LinkStatusColors {
|
||||
disabled: red,
|
||||
connected: green,
|
||||
listening: yellow,
|
||||
},
|
||||
syntax: SyntaxColors {
|
||||
gap_bg: darker_bg,
|
||||
executed_bg: Color::Rgb(45, 45, 60),
|
||||
selected_bg: Color::Rgb(70, 60, 50),
|
||||
emit: (fg, Color::Rgb(70, 50, 65)),
|
||||
number: (purple, Color::Rgb(55, 50, 70)),
|
||||
string: (green, Color::Rgb(50, 60, 50)),
|
||||
comment: (comment, darker_bg),
|
||||
keyword: (purple, Color::Rgb(60, 50, 70)),
|
||||
stack_op: (cyan, Color::Rgb(45, 60, 75)),
|
||||
operator: (red, Color::Rgb(65, 45, 55)),
|
||||
sound: (blue, Color::Rgb(45, 55, 70)),
|
||||
param: (orange, Color::Rgb(70, 55, 45)),
|
||||
context: (orange, Color::Rgb(70, 55, 45)),
|
||||
note: (green, Color::Rgb(50, 60, 45)),
|
||||
interval: (Color::Rgb(180, 220, 130), Color::Rgb(50, 65, 45)),
|
||||
variable: (green, Color::Rgb(50, 60, 45)),
|
||||
vary: (yellow, Color::Rgb(70, 60, 45)),
|
||||
generator: (cyan, Color::Rgb(45, 60, 75)),
|
||||
default: (fg_dim, darker_bg),
|
||||
},
|
||||
table: TableColors {
|
||||
row_even: darker_bg,
|
||||
row_odd: bg,
|
||||
},
|
||||
values: ValuesColors {
|
||||
tempo: orange,
|
||||
value: fg_dim,
|
||||
},
|
||||
hint: HintColors {
|
||||
key: orange,
|
||||
text: comment,
|
||||
},
|
||||
view_badge: ViewBadgeColors { bg: fg, fg: bg },
|
||||
nav: NavColors {
|
||||
selected_bg: Color::Rgb(65, 55, 80),
|
||||
selected_fg: fg,
|
||||
unselected_bg: bg_light,
|
||||
unselected_fg: comment,
|
||||
},
|
||||
editor_widget: EditorWidgetColors {
|
||||
cursor_bg: fg,
|
||||
cursor_fg: bg,
|
||||
selection_bg: Color::Rgb(60, 60, 80),
|
||||
completion_bg: bg_light,
|
||||
completion_fg: fg,
|
||||
completion_selected: orange,
|
||||
completion_example: cyan,
|
||||
},
|
||||
browser: BrowserColors {
|
||||
directory: blue,
|
||||
project_file: purple,
|
||||
selected: orange,
|
||||
file: fg,
|
||||
focused_border: orange,
|
||||
unfocused_border: comment,
|
||||
root: fg,
|
||||
file_icon: comment,
|
||||
folder_icon: blue,
|
||||
empty_text: comment,
|
||||
},
|
||||
input: InputColors {
|
||||
text: blue,
|
||||
cursor: fg,
|
||||
hint: comment,
|
||||
},
|
||||
search: SearchColors {
|
||||
active: orange,
|
||||
inactive: comment,
|
||||
match_bg: yellow,
|
||||
match_fg: bg,
|
||||
},
|
||||
markdown: MarkdownColors {
|
||||
h1: blue,
|
||||
h2: orange,
|
||||
h3: purple,
|
||||
code: green,
|
||||
code_border: Color::Rgb(70, 75, 95),
|
||||
link: red,
|
||||
link_url: Color::Rgb(110, 120, 160),
|
||||
quote: comment,
|
||||
text: fg,
|
||||
list: fg,
|
||||
},
|
||||
engine: EngineColors {
|
||||
header: blue,
|
||||
header_focused: yellow,
|
||||
divider: Color::Rgb(65, 70, 90),
|
||||
scroll_indicator: Color::Rgb(80, 85, 110),
|
||||
label: Color::Rgb(130, 140, 175),
|
||||
label_focused: Color::Rgb(160, 170, 200),
|
||||
label_dim: Color::Rgb(100, 110, 145),
|
||||
value: Color::Rgb(190, 195, 220),
|
||||
focused: yellow,
|
||||
normal: fg,
|
||||
dim: Color::Rgb(80, 85, 110),
|
||||
path: Color::Rgb(130, 140, 175),
|
||||
border_magenta: purple,
|
||||
border_green: green,
|
||||
border_cyan: cyan,
|
||||
separator: Color::Rgb(65, 70, 90),
|
||||
hint_active: Color::Rgb(210, 180, 100),
|
||||
hint_inactive: Color::Rgb(65, 70, 90),
|
||||
},
|
||||
dict: DictColors {
|
||||
word_name: green,
|
||||
word_bg: Color::Rgb(45, 55, 60),
|
||||
alias: comment,
|
||||
stack_sig: purple,
|
||||
description: fg,
|
||||
example: Color::Rgb(130, 140, 175),
|
||||
category_focused: yellow,
|
||||
category_selected: blue,
|
||||
category_normal: fg,
|
||||
category_dimmed: Color::Rgb(80, 85, 110),
|
||||
border_focused: yellow,
|
||||
border_normal: Color::Rgb(65, 70, 90),
|
||||
header_desc: Color::Rgb(150, 160, 190),
|
||||
},
|
||||
title: TitleColors {
|
||||
big_title: purple,
|
||||
author: blue,
|
||||
link: green,
|
||||
license: orange,
|
||||
prompt: Color::Rgb(150, 160, 190),
|
||||
subtitle: fg,
|
||||
},
|
||||
meter: MeterColors {
|
||||
low: green,
|
||||
mid: yellow,
|
||||
high: red,
|
||||
low_rgb: (158, 206, 106),
|
||||
mid_rgb: (224, 175, 104),
|
||||
high_rgb: (247, 118, 142),
|
||||
},
|
||||
sparkle: SparkleColors {
|
||||
colors: [
|
||||
(125, 207, 255),
|
||||
(224, 175, 104),
|
||||
(158, 206, 106),
|
||||
(247, 118, 142),
|
||||
(187, 154, 247),
|
||||
],
|
||||
},
|
||||
confirm: ConfirmColors {
|
||||
border: orange,
|
||||
button_selected_bg: orange,
|
||||
button_selected_fg: bg,
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::theme::meter;
|
||||
use crate::theme;
|
||||
use ratatui::buffer::Buffer;
|
||||
use ratatui::layout::Rect;
|
||||
use ratatui::style::Color;
|
||||
@@ -30,13 +30,13 @@ impl VuMeter {
|
||||
(db - DB_MIN) / DB_RANGE
|
||||
}
|
||||
|
||||
fn row_to_color(row_position: f32) -> Color {
|
||||
fn row_to_color(row_position: f32, colors: &theme::ThemeColors) -> Color {
|
||||
if row_position > 0.9 {
|
||||
meter::HIGH
|
||||
colors.meter.high
|
||||
} else if row_position > 0.75 {
|
||||
meter::MID
|
||||
colors.meter.mid
|
||||
} else {
|
||||
meter::LOW
|
||||
colors.meter.low
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -47,6 +47,7 @@ impl Widget for VuMeter {
|
||||
return;
|
||||
}
|
||||
|
||||
let colors = theme::get();
|
||||
let height = area.height as usize;
|
||||
let half_width = area.width / 2;
|
||||
let gap = 1u16;
|
||||
@@ -62,7 +63,7 @@ impl Widget for VuMeter {
|
||||
for row in 0..height {
|
||||
let y = area.y + area.height - 1 - row as u16;
|
||||
let row_position = (row as f32 + 0.5) / height as f32;
|
||||
let color = Self::row_to_color(row_position);
|
||||
let color = Self::row_to_color(row_position, &colors);
|
||||
|
||||
for col in 0..half_width.saturating_sub(gap) {
|
||||
let x = area.x + col;
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
# About Forth
|
||||
|
||||
Forth is a stack-based programming language created by Charles H. Moore in the early 1970s. It was designed for simplicity, directness, and interactive exploration. Forth has been used for many years to do scientific work and program embedded systems: controlling telescopes, running on devices used in space missions, etc. Forth quickly evolved into multiple implementations. None of them really reached an incredible popularity. Nonetheless, the ideas behind Forth continue to garner the interest of many different people in very different (often unrelated) fields. Nowadays, Forth languages are sometimes used by hackers and artists for their peculiarities. A Forth implementation is often simple, direct and beautiful. Forth is an elegant and minimal language to learn. It is easy to understand, to extend and to apply to a specific task. The Forth we use in Cagire is specialized in making live music.
|
||||
Forth is a _stack-based_ programming language created by Charles H. Moore in the early 1970s. It was designed with simplicity, directness, and interactive exploration in mind. Forth has been used for many years to do scientific work and program embedded systems: it was used to control telescopes and was running on some devices used in space missions among other things. Forth quickly evolved into multiple implementations targetting various computer architectures. None of them really took off and became popular. Nonetheless, the ideas behind Forth continue to garner the interest of many different people in very different (often unrelated) fields. Nowadays, Forth languages are used by hackers and artists for their peculiarity. Forth is simple, direct and beautiful to implement. Forth is an elegant and minimal language to learn. It is easy to understand, to extend and to apply to a specific task. The Forth we use in Cagire is specialized in making live music. We think of it as a DSL: a _Domain Specific Language_.
|
||||
|
||||
## Why Forth?
|
||||
|
||||
Most programming languages nowadays use a complex syntax made of `variables`, `expressions` and `statements` like `x = 3 + 4`. Forth works differently. It is way more simple than that, has almost no syntax, and performs computations in a quite unique way. You push values onto a `stack` and apply `words` that transform them:
|
||||
Most programming languages nowadays use a complex syntax made of `variables`, `expressions` and `statements` like `x = 3 + 4`. Forth works differently. It is way more simple than that, has almost no syntax and performs computations in a quite unique way. You push values onto a `stack` and apply `words` that transform them:
|
||||
|
||||
```forth
|
||||
3 4 +
|
||||
```
|
||||
|
||||
This program leaves the number `7` on the stack. There are no variables, no parentheses, no syntax to remember. You just end up with words and numbers separated by spaces. For live coding music, this directness is quite exciting. All you do is thinking in terms of transformations and adding things to the stack: take a note, shift it up, add reverb, play it.
|
||||
This program leaves the number `7` on the stack. There are no variables, no parentheses, no syntax to remember. You just end up with words and numbers separated by spaces. For live coding music, this directness is quite exciting. All you do is think in terms of transformations and add things to the stack: take a note, shift it up, add reverb, play it.
|
||||
|
||||
## The Stack
|
||||
|
||||
@@ -40,7 +40,7 @@ Words compose naturally on the stack. To double a number:
|
||||
3 dup + ( 3 3 +)
|
||||
```
|
||||
|
||||
There are a lot of words in a Forth, and thus, Cagire has a `Dictionary` embedded directly into the application. You can also create your own words. They will work just like the already existing words. There are good reasons to create new words on-the-fly:
|
||||
There are a lot of words in a Forth and thus, Cagire has a `Dictionary` embedded directly into the application. You can also create your own words. They will work just like the already existing words. There are good reasons to create new words on-the-fly:
|
||||
|
||||
- To make synth definitions.
|
||||
- To abstract _some piece of code_ that you use frequently.
|
||||
@@ -69,11 +69,9 @@ For example, `+` has the signature `( a b -- sum )`. It takes two values and lea
|
||||
|
||||
## The Command Register
|
||||
|
||||
Traditional Forth programs print text to a terminal. Cagire's Forth builds sound commands instead. This happens through an invisible accumulator called the command register.
|
||||
|
||||
The command register has two parts:
|
||||
- A **sound name** (what instrument to play)
|
||||
- A list of **parameters** (how to play it)
|
||||
Traditional Forth programs print text to a terminal. Cagire's Forth builds sound commands instead. This happens through an invisible accumulator called the command register. The command register has two parts:
|
||||
- a **sound name** (what instrument to play)
|
||||
- a list of **parameters** (how to play it)
|
||||
|
||||
Three types of words interact with it:
|
||||
|
||||
@@ -102,7 +100,7 @@ Each line adds something to the register. The final `.` triggers the sound. You
|
||||
"sine" s c4 note 0.5 gain 0.3 decay 0.4 verb .
|
||||
```
|
||||
|
||||
The order of parameters does not matter. You can even emit multiple times in a single step: If you need to discard the register without emitting, use `clear`:
|
||||
The order of parameters does not matter. You can even emit multiple times in a single step. If you need to discard the register without emitting, use `clear`:
|
||||
|
||||
```forth
|
||||
"kick" s 0.5 gain clear ;; nothing plays, register is emptied
|
||||
@@ -110,3 +108,8 @@ The order of parameters does not matter. You can even emit multiple times in a s
|
||||
```
|
||||
|
||||
This is useful when conditionals might cancel a sound before it emits.
|
||||
|
||||
## More details
|
||||
|
||||
- Each step has its own stack and independant runtime.
|
||||
- Word definitions and variable definitions are shared by all steps.
|
||||
|
||||
@@ -1,84 +0,0 @@
|
||||
# Arithmetic
|
||||
|
||||
Basic math operations. All arithmetic words pop their operands and push the result.
|
||||
|
||||
## Basic Operations
|
||||
|
||||
```
|
||||
3 4 + ( 7 )
|
||||
10 3 - ( 7 )
|
||||
3 4 * ( 12 )
|
||||
10 3 / ( 3.333... )
|
||||
10 3 mod ( 1 )
|
||||
```
|
||||
|
||||
Division always produces a float. Use `floor` if you need an integer result.
|
||||
|
||||
## Negative Numbers
|
||||
|
||||
```
|
||||
5 neg ( -5 )
|
||||
-3 abs ( 3 )
|
||||
```
|
||||
|
||||
## Rounding
|
||||
|
||||
```
|
||||
3.7 floor ( 3 )
|
||||
3.2 ceil ( 4 )
|
||||
3.5 round ( 4 )
|
||||
```
|
||||
|
||||
## Min and Max
|
||||
|
||||
```
|
||||
3 7 min ( 3 )
|
||||
3 7 max ( 7 )
|
||||
```
|
||||
|
||||
## Power and Root
|
||||
|
||||
```
|
||||
2 3 pow ( 8 )
|
||||
9 sqrt ( 3 )
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
Calculate a frequency ratio:
|
||||
|
||||
```
|
||||
440 2 12 / pow * ( 440 * 2^(1/12) ≈ 466.16 )
|
||||
```
|
||||
|
||||
Clamp a value between 0 and 1:
|
||||
|
||||
```
|
||||
1.5 0 max 1 min ( 1 )
|
||||
-0.5 0 max 1 min ( 0 )
|
||||
```
|
||||
|
||||
Scale a 0-1 range to 200-800:
|
||||
|
||||
```
|
||||
0.5 600 * 200 + ( 500 )
|
||||
```
|
||||
|
||||
## Words
|
||||
|
||||
| Word | Stack | Description |
|
||||
|------|-------|-------------|
|
||||
| `+` | (a b -- sum) | Add |
|
||||
| `-` | (a b -- diff) | Subtract |
|
||||
| `*` | (a b -- prod) | Multiply |
|
||||
| `/` | (a b -- quot) | Divide |
|
||||
| `mod` | (a b -- rem) | Modulo |
|
||||
| `neg` | (a -- -a) | Negate |
|
||||
| `abs` | (a -- \|a\|) | Absolute value |
|
||||
| `floor` | (a -- n) | Round down |
|
||||
| `ceil` | (a -- n) | Round up |
|
||||
| `round` | (a -- n) | Round to nearest |
|
||||
| `min` | (a b -- min) | Minimum |
|
||||
| `max` | (a b -- max) | Maximum |
|
||||
| `pow` | (a b -- a^b) | Power |
|
||||
| `sqrt` | (a -- √a) | Square root |
|
||||
@@ -1,73 +0,0 @@
|
||||
# Audio Engine
|
||||
|
||||
Cagire uses **Doux** as its audio engine ([https://doux.livecoding.fr](https://doux.livecoding.fr)). Doux is a standalone synthesis and sampling engine that receives commands as strings and turns them into sound. Doux is a fixed graph synthesizer, which means that the structure of the sound is defined by a fixed set of nodes and connections, and the parameters of these nodes can be adjusted to create different sounds. Doux is extremely versatile and you are likely to find it useful for a wide range of musical styles and genres.
|
||||
|
||||
## How Sound is Produced
|
||||
|
||||
When the sequencer hits an active step, the Forth script is compiled and executed. Each emit operation (`.`) generates a command string that is sent to Doux. The command encodes the sound name and all accumulated parameters. The following example script:
|
||||
|
||||
```
|
||||
"saw" sound c4 note 0.5 gain 2000 lpf .
|
||||
```
|
||||
|
||||
will produce a command string that Doux interprets to _play a saw wave at C4 with gain 0.5 and a 2kHz lowpass filter_.
|
||||
|
||||
## Sound sources
|
||||
|
||||
Each sound needs a source. Sources are defined by typing their name followed by the `sound` keyword. Sources are raw waveforms or samples. They are shaped by passing additional parameters that will modify the characteristics of the sound: envelopes, effects, synthesis options, etc. The following example defines a source named `saw` with a frequency of 440 Hz, a gain of 0.5 and some reverb:
|
||||
|
||||
```
|
||||
"saw" source 440 freq 0.5 gain 0.5 verb .
|
||||
```
|
||||
|
||||
The audio engine offers a vast array (~20+) of sources including oscillators, noises, live input, and more.
|
||||
|
||||
## Settings
|
||||
|
||||
- **Channels**: Output channel count (1-64)
|
||||
- **Buffer Size**: Audio buffer in samples (64-4096). Lower values reduce latency but increase CPU load.
|
||||
- **Voices**: Maximum polyphony (1-128, default 32). When the limit is reached, the oldest voice is stolen.
|
||||
|
||||
Settings are persisted across sessions.
|
||||
|
||||
## Samples
|
||||
|
||||
Cagire scans sample directories recursively and indexes all audio files. Add sample paths on the Engine page with **a**, remove with **d**. Use samples in scripts by name:
|
||||
|
||||
```
|
||||
"kick" s .
|
||||
"hat" s 0.5 gain .
|
||||
```
|
||||
|
||||
The sample index is shown on the Engine page with the total count.
|
||||
|
||||
## Visualizers
|
||||
|
||||
The Engine page displays two real-time visualizers:
|
||||
|
||||
- **Scope**: Waveform display (64 samples), updated on every audio callback
|
||||
- **Spectrum**: 32-band FFT analyzer with logarithmic frequency scaling (20Hz to Nyquist), Hann window, displayed in dB
|
||||
|
||||
Both can be toggled on or off in the Options page.
|
||||
|
||||
## Monitoring
|
||||
|
||||
The Engine page shows live metrics:
|
||||
|
||||
- **Active voices**: Current polyphony count
|
||||
- **Peak voices**: Highest voice count since last reset (press **r** to reset)
|
||||
- **CPU load**: Audio thread utilization
|
||||
- **Events**: Total emitted and dropped event counts
|
||||
|
||||
## Tempo Scaling
|
||||
|
||||
Some parameters are automatically scaled by step duration so they sound consistent across tempos. These include envelope times (attack, decay, release), filter envelopes, pitch envelopes, FM envelopes, glide, and reverb/delay times.
|
||||
|
||||
## Commands
|
||||
|
||||
On the Engine page:
|
||||
|
||||
- **h**: Hush (graceful fade-out of all voices)
|
||||
- **p**: Panic (hard stop all voices immediately)
|
||||
- **r**: Reset peak voice counter
|
||||
- **t**: Test sound (plays a 440Hz sine)
|
||||
@@ -1,2 +0,0 @@
|
||||
# Chaining
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
# Chords
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
# Comparison
|
||||
|
||||
Compare values and produce boolean results (0 or 1).
|
||||
|
||||
## Equality
|
||||
|
||||
```forth
|
||||
3 3 = ( 1 - equal )
|
||||
3 4 = ( 0 - not equal )
|
||||
3 4 != ( 1 - not equal )
|
||||
3 4 <> ( 1 - not equal, alternative )
|
||||
```
|
||||
|
||||
## Ordering
|
||||
|
||||
```forth
|
||||
2 3 lt ( 1 - less than )
|
||||
3 2 gt ( 1 - greater than )
|
||||
3 3 <= ( 1 - less or equal )
|
||||
3 3 >= ( 1 - greater or equal )
|
||||
```
|
||||
|
||||
## With Conditionals
|
||||
|
||||
```forth
|
||||
step 4 lt { "kick" s . } ? ( kick on first 4 steps )
|
||||
|
||||
beat 8 >= { 0.5 gain } ? ( quieter after beat 8 )
|
||||
```
|
||||
|
||||
## Words
|
||||
|
||||
| Word | Stack | Description |
|
||||
|------|-------|-------------|
|
||||
| `=` | (a b -- bool) | Equal |
|
||||
| `!=` | (a b -- bool) | Not equal |
|
||||
| `<>` | (a b -- bool) | Not equal (alias) |
|
||||
| `lt` | (a b -- bool) | Less than |
|
||||
| `gt` | (a b -- bool) | Greater than |
|
||||
| `<=` | (a b -- bool) | Less or equal |
|
||||
| `>=` | (a b -- bool) | Greater or equal |
|
||||
@@ -1,2 +0,0 @@
|
||||
# Conditionals
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
# Context
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
# Cycles
|
||||
|
||||
@@ -1,2 +1,95 @@
|
||||
# Custom Words
|
||||
# Creating Words
|
||||
|
||||
One of Forth's most powerful features is the ability to define new words. A word definition gives a name to a sequence of operations. Once defined, you can use the new word just like any built-in word.
|
||||
|
||||
## The Syntax
|
||||
|
||||
Use `:` to start a definition and `;` to end it:
|
||||
|
||||
```forth
|
||||
: double dup + ;
|
||||
```
|
||||
|
||||
This creates a word called `double` that duplicates the top value and adds it to itself. Now you can use it:
|
||||
|
||||
```forth
|
||||
3 double ;; leaves 6 on the stack
|
||||
5 double ;; leaves 10 on the stack
|
||||
```
|
||||
|
||||
The definition is simple: everything between `:` and `;` becomes the body of the word.
|
||||
|
||||
## Definitions Are Shared
|
||||
|
||||
When you define a word in one step, it becomes available to all other steps. This is how you share code across your pattern. Define your synths, rhythms, and utilities once, then use them everywhere.
|
||||
|
||||
Step 0:
|
||||
```forth
|
||||
: bass "saw" s 0.8 gain 800 lpf ;
|
||||
```
|
||||
|
||||
Step 4:
|
||||
```forth
|
||||
c2 note bass .
|
||||
```
|
||||
|
||||
Step 8:
|
||||
```forth
|
||||
g2 note bass .
|
||||
```
|
||||
|
||||
The `bass` word carries the sound design. Each step just adds a note and plays.
|
||||
|
||||
## Redefining Words
|
||||
|
||||
You can redefine any word, including built-in ones:
|
||||
|
||||
```forth
|
||||
: dup drop ;
|
||||
```
|
||||
|
||||
Now `dup` does the opposite of what it used to do. This is powerful but dangerous. Redefining core words can break things in subtle ways.
|
||||
|
||||
You can even redefine numbers:
|
||||
|
||||
```forth
|
||||
: 2 4 ;
|
||||
```
|
||||
|
||||
Now `2` pushes `4` onto the stack. The number two no longer exists in your session. This is a classic Forth demonstration: nothing is sacred, everything can be redefined.
|
||||
|
||||
## Practical Uses
|
||||
|
||||
**Synth definitions** save you from repeating sound design:
|
||||
|
||||
```forth
|
||||
: pad "sine" s 0.3 gain 2 attack 0.5 verb ;
|
||||
```
|
||||
|
||||
**Transpositions** and musical helpers:
|
||||
|
||||
```forth
|
||||
: octup 12 + ;
|
||||
: octdn 12 - ;
|
||||
```
|
||||
|
||||
## Words That Emit
|
||||
|
||||
A word can contain `.` to emit sounds directly:
|
||||
|
||||
```forth
|
||||
: kick "kick" s . ;
|
||||
: hat "hat" s 0.4 gain . ;
|
||||
```
|
||||
|
||||
Then a step becomes trivial:
|
||||
|
||||
```forth
|
||||
kick hat
|
||||
```
|
||||
|
||||
Two sounds, two words, no clutter.
|
||||
|
||||
## Stack Effects
|
||||
|
||||
When you create a word, think about what it expects on the stack and what it leaves behind. The word `double` expects one number and leaves one number. The word `kick` expects nothing and leaves nothing (it emits a sound as a side effect). Well-designed words have clear stack effects. This makes them easy to combine.
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
# Delay & Reverb
|
||||
|
||||
Add space and depth to your sounds with time-based effects.
|
||||
|
||||
## Delay
|
||||
|
||||
```forth
|
||||
"snare" s 0.3 delay . ( delay mix )
|
||||
"snare" s 0.25 delaytime . ( delay time in seconds )
|
||||
"snare" s 0.5 delayfeedback . ( feedback amount )
|
||||
"snare" s 1 delaytype . ( delay type )
|
||||
```
|
||||
|
||||
## Reverb
|
||||
|
||||
```forth
|
||||
"pad" s 0.3 verb . ( reverb mix )
|
||||
"pad" s 2 verbdecay . ( decay time )
|
||||
"pad" s 0.5 verbdamp . ( high frequency damping )
|
||||
"pad" s 0.02 verbpredelay . ( predelay time )
|
||||
"pad" s 0.7 verbdiff . ( diffusion )
|
||||
"pad" s 1 size . ( room size )
|
||||
```
|
||||
|
||||
## Combined Example
|
||||
|
||||
```forth
|
||||
"keys" s
|
||||
c4 note
|
||||
0.2 delay
|
||||
0.375 delaytime
|
||||
0.4 delayfeedback
|
||||
0.25 verb
|
||||
1.5 verbdecay
|
||||
.
|
||||
```
|
||||
|
||||
## Words
|
||||
|
||||
| Word | Stack | Description |
|
||||
|------|-------|-------------|
|
||||
| `delay` | (f --) | Set delay mix |
|
||||
| `delaytime` | (f --) | Set delay time |
|
||||
| `delayfeedback` | (f --) | Set delay feedback |
|
||||
| `delaytype` | (n --) | Set delay type |
|
||||
| `verb` | (f --) | Set reverb mix |
|
||||
| `verbdecay` | (f --) | Set reverb decay |
|
||||
| `verbdamp` | (f --) | Set reverb damping |
|
||||
| `verbpredelay` | (f --) | Set reverb predelay |
|
||||
| `verbdiff` | (f --) | Set reverb diffusion |
|
||||
| `size` | (f --) | Set reverb size |
|
||||
@@ -6,13 +6,16 @@ Cagire includes a built-in dictionary of all the internal Forth words. Press `Ct
|
||||
|
||||
The dictionary shows every available word organized by category:
|
||||
|
||||
- **Stack**: Manipulation words like `dup`, `swap`, `drop`
|
||||
- **Arithmetic**: Math operations
|
||||
- **Sound**: Sound sources and emission
|
||||
- **Filter**, **Envelope**, **Effects**: Sound shaping
|
||||
- **Context**: Sequencer state like `step`, `beat`, `tempo`
|
||||
- **Stack**: Manipulation words like `dup`, `swap`, `drop`.
|
||||
- **Arithmetic**: Math operations.
|
||||
- **Sound**: Sound sources and emission.
|
||||
- **Filter**, **Envelope**, **Effects**: Sound shaping.
|
||||
- **MIDI**: External MIDI control (`chan`, `cc`, `emit`, `clock`, etc.).
|
||||
- **Context**: Sequencer state like `step`, `beat`, `tempo`.
|
||||
- And many more...
|
||||
|
||||
This tutorial will not teach you how to use all words. The syntax is very uniform and you can quickly learn a new word when necessary. We encourage you to explore as you play, this is the best way to learn. The tutorial will remain focused on various topics that require you to apply knowledge to a given task or specific context.
|
||||
|
||||
## Navigation
|
||||
|
||||
| Key | Action |
|
||||
@@ -23,8 +26,6 @@ The dictionary shows every available word organized by category:
|
||||
| `/` or `Ctrl+F` | Search |
|
||||
| `Esc` | Clear search |
|
||||
|
||||
## Word Information
|
||||
|
||||
Each word entry shows:
|
||||
|
||||
- **Name** and aliases
|
||||
@@ -32,12 +33,6 @@ Each word entry shows:
|
||||
- **Description**: What the word does
|
||||
- **Example**: How to use it
|
||||
|
||||
## Search
|
||||
|
||||
Press `/` to search across all words. The search matches word names, aliases, and descriptions. Press `Esc` to clear and return to browsing.
|
||||
|
||||
## Tips
|
||||
|
||||
- Use the dictionary while writing scripts to check stack effects
|
||||
- Categories group related words together
|
||||
- Some words have shorter aliases (e.g., `sound` → `s`)
|
||||
Use the dictionary while writing scripts to check stack effects and study their behavior. Some words also come with shorter aliases (e.g., `sound` → `s`). You will learn aliases quite naturally, because aliases are usually reserved for very common words.
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
# Effects
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
# Emitting Sounds
|
||||
|
||||
The core of Cagire is emitting sounds. Every step script builds up sound commands and emits them to the audio engine.
|
||||
|
||||
## The Sound Register
|
||||
|
||||
Before emitting, you must select a sound source using `sound` (or its alias `s`):
|
||||
|
||||
```forth
|
||||
"kick" sound
|
||||
"kick" s ( same thing, shorter )
|
||||
```
|
||||
|
||||
This sets the current sound register. All subsequent parameter words modify this sound until you emit or clear it.
|
||||
|
||||
## Emitting
|
||||
|
||||
The `.` word emits the current sound:
|
||||
|
||||
```forth
|
||||
"kick" s . ( emit one kick )
|
||||
"kick" s . . . ( emit three kicks )
|
||||
```
|
||||
|
||||
Use `.!` to emit multiple times:
|
||||
|
||||
```forth
|
||||
"kick" s 4 .! ( emit four kicks )
|
||||
```
|
||||
|
||||
## Clearing
|
||||
|
||||
The `clear` word resets the sound register and all parameters:
|
||||
|
||||
```forth
|
||||
"kick" s 0.5 gain . clear "hat" s .
|
||||
```
|
||||
|
||||
This is useful when you want to emit different sounds with independent parameters in the same step.
|
||||
|
||||
## Words
|
||||
|
||||
| Word | Stack | Description |
|
||||
|------|-------|-------------|
|
||||
| `sound` / `s` | (name --) | Set sound source |
|
||||
| `.` | (--) | Emit current sound |
|
||||
| `.!` | (n --) | Emit current sound n times |
|
||||
| `clear` | (--) | Clear sound register and params |
|
||||
65
docs/engine_distortion.md
Normal file
65
docs/engine_distortion.md
Normal file
@@ -0,0 +1,65 @@
|
||||
# Distortion
|
||||
|
||||
Distortion effects add harmonics by nonlinearly shaping the waveform.
|
||||
|
||||
## Saturation
|
||||
|
||||
Soft saturation using the transfer function `x / (1 + k|x|)`.
|
||||
|
||||
```forth
|
||||
saw 2 distort .
|
||||
saw 8 distort 0.5 distortvol . ( with volume compensation )
|
||||
```
|
||||
|
||||
| Parameter | Range | Description |
|
||||
|-----------|-------|-------------|
|
||||
| `distort` | 0+ | Saturation amount |
|
||||
| `distortvol` | 0-1 | Output volume |
|
||||
|
||||
## Wavefolding
|
||||
|
||||
Wavefolding reflects the signal when it exceeds ±1, using `sin(x × amount × π/2)`.
|
||||
|
||||
```forth
|
||||
sine 4 fold .
|
||||
```
|
||||
|
||||
| Parameter | Range | Description |
|
||||
|-----------|-------|-------------|
|
||||
| `fold` | 0+ | Fold amount |
|
||||
|
||||
## Wavewrapping
|
||||
|
||||
Wavewrapping applies modulo to wrap the signal into the -1 to 1 range.
|
||||
|
||||
```forth
|
||||
saw 3 wrap .
|
||||
```
|
||||
|
||||
| Parameter | Range | Description |
|
||||
|-----------|-------|-------------|
|
||||
| `wrap` | 0+ | Number of wraps |
|
||||
|
||||
## Bit Crushing
|
||||
|
||||
Bit crushing quantizes the signal to fewer amplitude levels.
|
||||
|
||||
```forth
|
||||
snare 6 crush . ( 6-bit = 32 levels )
|
||||
```
|
||||
|
||||
| Parameter | Range | Description |
|
||||
|-----------|-------|-------------|
|
||||
| `crush` | 1-16 | Bit depth |
|
||||
|
||||
## Sample Rate Reduction
|
||||
|
||||
Sample rate reduction holds each sample for multiple output samples.
|
||||
|
||||
```forth
|
||||
hat 4 coarse . ( 1/4 effective sample rate )
|
||||
```
|
||||
|
||||
| Parameter | Range | Description |
|
||||
|-----------|-------|-------------|
|
||||
| `coarse` | 1+ | Reduction factor (1 = bypass) |
|
||||
131
docs/engine_filters.md
Normal file
131
docs/engine_filters.md
Normal file
@@ -0,0 +1,131 @@
|
||||
# Filters
|
||||
|
||||
Filters attenuate frequencies above or below a cutoff point.
|
||||
|
||||
## Lowpass Filter
|
||||
|
||||
The lowpass filter (`lpf`) attenuates frequencies above the cutoff.
|
||||
|
||||
```forth
|
||||
saw 1000 lpf . ( cut above 1000 Hz )
|
||||
saw 500 lpf 0.8 lpq . ( with resonance )
|
||||
```
|
||||
|
||||
| Parameter | Range | Description |
|
||||
|-----------|-------|-------------|
|
||||
| `lpf` | Hz | Cutoff frequency |
|
||||
| `lpq` | 0-1 | Resonance (peak at cutoff) |
|
||||
|
||||
## Highpass Filter
|
||||
|
||||
The highpass filter (`hpf`) attenuates frequencies below the cutoff.
|
||||
|
||||
```forth
|
||||
kick 200 hpf . ( cut below 200 Hz )
|
||||
pad 400 hpf 0.3 hpq . ( with resonance )
|
||||
```
|
||||
|
||||
| Parameter | Range | Description |
|
||||
|-----------|-------|-------------|
|
||||
| `hpf` | Hz | Cutoff frequency |
|
||||
| `hpq` | 0-1 | Resonance |
|
||||
|
||||
## Bandpass Filter
|
||||
|
||||
The bandpass filter (`bpf`) attenuates frequencies outside a band around the center frequency.
|
||||
|
||||
```forth
|
||||
noise 1000 bpf 0.7 bpq . ( narrow band around 1000 Hz )
|
||||
```
|
||||
|
||||
| Parameter | Range | Description |
|
||||
|-----------|-------|-------------|
|
||||
| `bpf` | Hz | Center frequency |
|
||||
| `bpq` | 0-1 | Resonance (narrower band) |
|
||||
|
||||
## Filter Slope
|
||||
|
||||
The `ftype` parameter sets the filter slope (rolloff steepness).
|
||||
|
||||
| Value | Slope |
|
||||
|-------|-------|
|
||||
| `1` | 12 dB/octave |
|
||||
| `2` | 24 dB/octave (default) |
|
||||
| `3` | 48 dB/octave |
|
||||
|
||||
```forth
|
||||
saw 800 lpf 3 ftype . ( 48 dB/oct lowpass )
|
||||
```
|
||||
|
||||
## Filter Envelope
|
||||
|
||||
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`.
|
||||
|
||||
```forth
|
||||
saw 200 lpf 2 lpe 0.01 lpa 0.3 lpd . ( cutoff sweeps from 600 Hz down to 200 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.).
|
||||
|
||||
## Ladder Filters
|
||||
|
||||
Ladder filters use a different algorithm (Moog-style) with self-oscillation at high resonance.
|
||||
|
||||
```forth
|
||||
saw 800 llpf 0.7 llpq . ( ladder lowpass )
|
||||
saw 300 lhpf 0.5 lhpq . ( ladder highpass )
|
||||
saw 1000 lbpf 0.8 lbpq . ( ladder bandpass )
|
||||
```
|
||||
|
||||
| Parameter | Range | Description |
|
||||
|-----------|-------|-------------|
|
||||
| `llpf` | Hz | Ladder lowpass cutoff |
|
||||
| `llpq` | 0-1 | Ladder lowpass resonance |
|
||||
| `lhpf` | Hz | Ladder highpass cutoff |
|
||||
| `lhpq` | 0-1 | Ladder highpass resonance |
|
||||
| `lbpf` | Hz | Ladder bandpass cutoff |
|
||||
| `lbpq` | 0-1 | Ladder bandpass resonance |
|
||||
|
||||
Ladder filters share the lowpass envelope parameters (`lpe`, `lpa`, etc.).
|
||||
|
||||
## EQ
|
||||
|
||||
The 3-band EQ applies shelf and peak filters at fixed frequencies.
|
||||
|
||||
```forth
|
||||
kick 3 eqlo -2 eqhi . ( +3 dB at 200 Hz, -2 dB at 5000 Hz )
|
||||
snare 2 eqmid . ( +2 dB at 1000 Hz )
|
||||
```
|
||||
|
||||
| Parameter | Frequency | Type |
|
||||
|-----------|-----------|------|
|
||||
| `eqlo` | 200 Hz | Low shelf (dB) |
|
||||
| `eqmid` | 1000 Hz | Peak (dB) |
|
||||
| `eqhi` | 5000 Hz | High shelf (dB) |
|
||||
|
||||
## Tilt EQ
|
||||
|
||||
Tilt EQ applies a high shelf at 800 Hz with up to ±6 dB gain.
|
||||
|
||||
```forth
|
||||
pad -0.5 tilt . ( -3 dB above 800 Hz )
|
||||
hat 0.5 tilt . ( +3 dB above 800 Hz )
|
||||
```
|
||||
|
||||
| Parameter | Range | Description |
|
||||
|-----------|-------|-------------|
|
||||
| `tilt` | -1 to 1 | High shelf gain (-1 = -6 dB, 0 = flat, 1 = +6 dB) |
|
||||
55
docs/engine_intro.md
Normal file
55
docs/engine_intro.md
Normal file
@@ -0,0 +1,55 @@
|
||||
# Introduction
|
||||
|
||||
Cagire includes an audio engine called `Doux`. No external software is needed to make sound. `Doux` is an opinionated, semi-modular synthesizer. It was designed for live coding environments and works by receiving command strings that describe sounds. Despite its fixed architecture,`Doux` is extremely versatile and will likely cover most of the audio needs of a live coder.
|
||||
|
||||
## How It Works
|
||||
|
||||
When you write a Forth script and emit (`.`), the script produces a command string. This command travels to the audio engine, which interprets it and creates a voice. The voice plays until its envelope finishes or until it is killed by another voice. You can also spawn infinite voices, but you will need to manage their lifecycle manually, otherwise they will never stop.
|
||||
|
||||
```forth
|
||||
saw s c4 note 0.8 gain 0.3 verb .
|
||||
```
|
||||
|
||||
## Voices
|
||||
|
||||
Each `emit` (`.`) creates or manages a voice by sending parameters. Voices are independent sound generators with their own oscillator, envelope, and effects. The engine can run many voices at once (up to `128`, default `32`). When you exceed the voice limit, the oldest voice is stolen (a process called _round robin scheduling_). You can monitor voice usage on the Engine page:
|
||||
|
||||
- **Active voices**: how many are playing right now.
|
||||
- **Peak voices**: the maximum reached since last reset.
|
||||
|
||||
Press `r` on the Engine page to reset the peak counter.
|
||||
|
||||
## Parameters
|
||||
|
||||
After selecting a sound source, you add parameters. Each parameter word takes a value from the stack and stores it in the command register:
|
||||
|
||||
```forth
|
||||
saw s
|
||||
c4 note ;; pitch
|
||||
0.5 gain ;; volume
|
||||
0.1 attack ;; envelope attack time
|
||||
2000 lpf ;; lowpass filter at 2kHz
|
||||
0.3 verb ;; reverb mix
|
||||
.
|
||||
```
|
||||
|
||||
Parameters can appear in any order. They accumulate until you emit. You can clear the register using the `clear` word.
|
||||
|
||||
## Controlling Existing Voices
|
||||
|
||||
You can emit without a sound name. In this case, no new voice is created. Instead, the parameters are sent to control an existing voice. Use `voice` with an ID to target a specific voice:
|
||||
|
||||
```forth
|
||||
0 voice 500 freq . ;; change frequency on voice 0
|
||||
```
|
||||
|
||||
This is useful for modulating long-running or infinite voices. Set up a drone on one step with a known voice ID, then tweak its parameters from other steps.
|
||||
|
||||
## Hush and Panic
|
||||
|
||||
Two emergency controls exist on the Engine page:
|
||||
|
||||
- `h` - **Hush**: gracefully fade out all voices
|
||||
- `p` - **Panic**: immediately kill all voices
|
||||
|
||||
Use hush when things get too loud. Use panic when things go wrong.
|
||||
132
docs/engine_modulation.md
Normal file
132
docs/engine_modulation.md
Normal file
@@ -0,0 +1,132 @@
|
||||
# Modulation
|
||||
|
||||
Modulation effects vary parameters over time using LFOs or envelopes.
|
||||
|
||||
## Vibrato
|
||||
|
||||
Vibrato modulates pitch with an LFO.
|
||||
|
||||
```forth
|
||||
saw 5 vib 0.5 vibmod . ( 5 Hz, 0.5 semitone depth )
|
||||
```
|
||||
|
||||
| Parameter | Range | Description |
|
||||
|-----------|-------|-------------|
|
||||
| `vib` | Hz | LFO rate |
|
||||
| `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.
|
||||
|
||||
```forth
|
||||
sine 440 freq 2 fm 2 fmh . ( modulator at 2× carrier frequency )
|
||||
```
|
||||
|
||||
| Parameter | Range | Description |
|
||||
|-----------|-------|-------------|
|
||||
| `fm` | 0+ | Modulation index |
|
||||
| `fmh` | ratio | Harmonic ratio (modulator / carrier) |
|
||||
| `fmshape` | shape | Modulator waveform |
|
||||
|
||||
FM has its own envelope (`fme`, `fma`, `fmd`, `fms`, `fmr`).
|
||||
|
||||
## Amplitude Modulation
|
||||
|
||||
AM multiplies the signal by an LFO.
|
||||
|
||||
```forth
|
||||
pad 4 am 0.5 amdepth . ( 4 Hz tremolo )
|
||||
```
|
||||
|
||||
| Parameter | Range | Description |
|
||||
|-----------|-------|-------------|
|
||||
| `am` | Hz | LFO rate |
|
||||
| `amdepth` | 0-1 | Modulation depth |
|
||||
| `amshape` | shape | LFO waveform |
|
||||
|
||||
## Ring Modulation
|
||||
|
||||
Ring modulation multiplies two signals, producing sum and difference frequencies.
|
||||
|
||||
```forth
|
||||
saw 150 rm 0.8 rmdepth . ( ring mod at 150 Hz )
|
||||
```
|
||||
|
||||
| Parameter | Range | Description |
|
||||
|-----------|-------|-------------|
|
||||
| `rm` | Hz | Modulator frequency |
|
||||
| `rmdepth` | 0-1 | Modulation depth |
|
||||
| `rmshape` | shape | Modulator waveform |
|
||||
|
||||
## Phaser
|
||||
|
||||
Phaser sweeps notches through the frequency spectrum using allpass filters.
|
||||
|
||||
```forth
|
||||
pad 0.5 phaser 0.6 phaserdepth .
|
||||
```
|
||||
|
||||
| Parameter | Range | Description |
|
||||
|-----------|-------|-------------|
|
||||
| `phaser` | Hz | Sweep rate |
|
||||
| `phaserdepth` | 0-1 | Sweep depth |
|
||||
| `phasersweep` | cents | Sweep range |
|
||||
| `phasercenter` | Hz | Center frequency |
|
||||
|
||||
## Flanger
|
||||
|
||||
Flanger mixes the signal with a short modulated delay (0.5-10ms).
|
||||
|
||||
```forth
|
||||
pad 0.3 flanger 0.7 flangerdepth 0.5 flangerfeedback .
|
||||
```
|
||||
|
||||
| Parameter | Range | Description |
|
||||
|-----------|-------|-------------|
|
||||
| `flanger` | Hz | Modulation rate |
|
||||
| `flangerdepth` | 0-1 | Modulation depth |
|
||||
| `flangerfeedback` | 0-0.95 | Feedback amount |
|
||||
|
||||
## Chorus
|
||||
|
||||
Chorus uses multiple modulated delay lines with 120° phase offset for stereo width.
|
||||
|
||||
```forth
|
||||
pad 1 chorus 0.4 chorusdepth 20 chorusdelay .
|
||||
```
|
||||
|
||||
| Parameter | Range | Description |
|
||||
|-----------|-------|-------------|
|
||||
| `chorus` | Hz | Modulation rate |
|
||||
| `chorusdepth` | 0-1 | Modulation depth |
|
||||
| `chorusdelay` | ms | Base delay time |
|
||||
126
docs/engine_samples.md
Normal file
126
docs/engine_samples.md
Normal file
@@ -0,0 +1,126 @@
|
||||
# Samples
|
||||
|
||||
The `sample` source plays audio files from disk with pitch tracking.
|
||||
|
||||
## Loading Samples
|
||||
|
||||
There are two ways to load samples:
|
||||
|
||||
* **From the app:** Navigate to the Engine view and find the Samples section. Press `A` to open a file browser, then select a folder containing your samples. Press `D` to remove the last added path.
|
||||
|
||||
* **From the command line:** Use the `-s` flag when launching Cagire:
|
||||
|
||||
```
|
||||
cagire -s ~/samples -s ~/more-samples
|
||||
```
|
||||
|
||||
The engine scans these directories and builds a registry of available samples. Samples load in the background without blocking audio. Supported file formats are `.wav`, `.mp3`, `.ogg`, `.flac` and `.aiff`.
|
||||
|
||||
## Folder Organization
|
||||
|
||||
```
|
||||
samples/
|
||||
├── kick.wav → "kick"
|
||||
├── snare.wav → "snare"
|
||||
└── hats/
|
||||
├── closed.wav → "hats" n 0
|
||||
├── open.wav → "hats" n 1
|
||||
└── pedal.wav → "hats" n 2
|
||||
```
|
||||
|
||||
Folders at the root of your directory are used as the name of a sample bank. Folders create sample banks where each file gets an index. Files are sorted alphabetically and assigned indices starting from `0`.
|
||||
|
||||
## Playing Samples
|
||||
|
||||
```forth
|
||||
kick sound . ( play kick sample )
|
||||
hats sound 2 n . ( play third hat sample )
|
||||
snare sound 0.5 speed . ( play snare at half speed )
|
||||
```
|
||||
|
||||
## Parameters
|
||||
|
||||
| Parameter | Range | Description |
|
||||
|-----------|-------|-------------|
|
||||
| `n` | 0+ | Sample index within a folder (wraps around) |
|
||||
| `begin` | 0-1 | Playback start position |
|
||||
| `end` | 0-1 | Playback end position |
|
||||
| `speed` | any | Playback speed multiplier |
|
||||
| `freq` | Hz | Base frequency for pitch tracking |
|
||||
| `fit` | seconds | Stretch/compress sample to fit duration |
|
||||
| `cut` | 0+ | Choke group |
|
||||
|
||||
## Slicing with Begin/End
|
||||
|
||||
The `begin` and `end` parameters define what portion of the sample plays. Values are normalized from 0 (start) to 1 (end).
|
||||
|
||||
```forth
|
||||
kick sound 0.25 begin 0.75 end . ( play middle half )
|
||||
kick sound 0.5 begin . ( play second half )
|
||||
kick sound 0.5 end . ( play first half )
|
||||
```
|
||||
|
||||
If begin is greater than end, they swap automatically.
|
||||
|
||||
## Speed and Pitch
|
||||
|
||||
The `speed` parameter affects both tempo and pitch. A speed of 2 plays twice as fast and an octave higher.
|
||||
|
||||
```forth
|
||||
snare sound 2 speed . ( double speed, octave up )
|
||||
snare sound 0.5 speed . ( half speed, octave down )
|
||||
snare sound -1 speed . ( play in reverse )
|
||||
```
|
||||
|
||||
For pitched playback, use `freq` or note names. The sample's base frequency defaults to middle C (261.626 Hz).
|
||||
|
||||
```forth
|
||||
kick sound 440 freq . ( play at A4 )
|
||||
kick sound c4 . ( play at C4 )
|
||||
```
|
||||
|
||||
Negative speed will reverse the sample and play it backwards.
|
||||
|
||||
```forth
|
||||
crow sound -1 speed . ( play backwards at nominal speed )
|
||||
crow sound -4 speed . ( play backwards, 4 times faster )
|
||||
```
|
||||
|
||||
## Fitting to Duration
|
||||
|
||||
The `fit` parameter stretches or compresses a sample to match a target duration in seconds. This adjusts speed automatically.
|
||||
|
||||
```forth
|
||||
kick sound 0.25 fit . ( fit kick into quarter second )
|
||||
snare sound beat fit . ( fit snare to one beat )
|
||||
```
|
||||
|
||||
## Choke Groups
|
||||
|
||||
The `cut` parameter assigns a sample to a choke group. When a new sample with the same cut value plays, it kills any currently playing samples in that group.
|
||||
|
||||
```forth
|
||||
hihat_closed sound 1 cut . ( choke group 1 )
|
||||
hihat_open sound 1 cut . ( kills closed hat, starts open )
|
||||
```
|
||||
|
||||
This is essential for realistic hi-hat behavior where open and closed hats shouldn't overlap.
|
||||
|
||||
## Bank Variations
|
||||
|
||||
Add `_suffix` to sample names to create variations that share the same base name.
|
||||
|
||||
```
|
||||
samples/
|
||||
├── kick.wav
|
||||
├── kick_hard.wav
|
||||
├── kick_soft.wav
|
||||
```
|
||||
|
||||
Select variations with the `bank` parameter:
|
||||
|
||||
```forth
|
||||
kick sound . ( plays kick.wav )
|
||||
kick sound hard bank . ( plays kick_hard.wav )
|
||||
kick sound soft bank . ( plays kick_soft.wav )
|
||||
```
|
||||
126
docs/engine_settings.md
Normal file
126
docs/engine_settings.md
Normal file
@@ -0,0 +1,126 @@
|
||||
# Settings
|
||||
|
||||
The audio engine can be configured through the Engine page or via command-line arguments. Settings are saved and restored between sessions.
|
||||
|
||||
## Engine Page
|
||||
|
||||
Press `Ctrl+Right` until you reach the Engine page. Here you can see the engine status and adjust settings.
|
||||
|
||||
### Display
|
||||
|
||||
The right side of the page shows visualizations:
|
||||
|
||||
- **Scope**: oscilloscope showing the audio waveform
|
||||
- **Spectrum**: 32-band frequency analyzer
|
||||
|
||||
### Settings
|
||||
|
||||
Navigate with arrow keys, adjust values with left/right:
|
||||
|
||||
- **Output Device**: where sound goes (speakers, headphones, interface).
|
||||
- **Input Device**: what audio input source to use (microphone, line-in, etc.).
|
||||
- **Channels**: number of output channels (2 for stereo).
|
||||
- **Buffer Size**: audio buffer in samples (64-4096).
|
||||
- **Max Voices**: polyphony limit (1-128, default 32).
|
||||
- **Lookahead**: scheduling lookahead in milliseconds (0-50, default 15).
|
||||
|
||||
### Buffer Size
|
||||
|
||||
Smaller buffers mean lower latency but higher CPU load. Larger buffers are safer but feel sluggish.
|
||||
|
||||
| Buffer | Latency at 44.1kHz |
|
||||
|--------|-------------------|
|
||||
| 64 | ~1.5ms |
|
||||
| 128 | ~3ms |
|
||||
| 256 | ~6ms |
|
||||
| 512 | ~12ms |
|
||||
| 1024 | ~23ms |
|
||||
|
||||
Start with 512. Lower it if you need tighter timing. Raise it if you hear glitches.
|
||||
|
||||
## Samples
|
||||
|
||||
The engine indexes audio files from your sample directories. Add directories with `A`, remove with `D`. The sample count shows how many files are indexed.
|
||||
|
||||
### Supported Formats
|
||||
|
||||
- WAV (.wav)
|
||||
- MP3 (.mp3)
|
||||
- OGG Vorbis (.ogg)
|
||||
- FLAC (.flac)
|
||||
- AIFF (.aiff, .aif)
|
||||
- AAC (.aac)
|
||||
- M4A (.m4a)
|
||||
|
||||
### Lazy Loading
|
||||
|
||||
Samples are not loaded into memory at startup. They are decoded on demand when first played. This means you can have thousands of samples indexed without using much RAM until you actually use them.
|
||||
|
||||
### Folder Organization
|
||||
|
||||
The scanner looks at top-level files and one level of subdirectories:
|
||||
|
||||
```
|
||||
samples/
|
||||
├── kick.wav -> "kick"
|
||||
├── snare.wav -> "snare"
|
||||
├── hats/
|
||||
│ ├── closed.wav -> "hats" n 0
|
||||
│ ├── open.wav -> "hats" n 1
|
||||
│ └── pedal.wav -> "hats" n 2
|
||||
└── breaks/
|
||||
├── amen.wav -> "breaks" n 0
|
||||
└── think.wav -> "breaks" n 1
|
||||
```
|
||||
|
||||
Top-level files are named by their filename (without extension). Files inside folders are sorted alphabetically and numbered starting from 0.
|
||||
|
||||
### Playing Samples
|
||||
|
||||
Reference samples by name:
|
||||
|
||||
```forth
|
||||
kick s . ;; play kick.wav
|
||||
snare s 0.5 gain . ;; play snare at half volume
|
||||
```
|
||||
|
||||
For samples in folders, use `n` to select which one:
|
||||
|
||||
```forth
|
||||
hats s 0 n . ;; play hats/closed.wav (index 0)
|
||||
hats s 1 n . ;; play hats/open.wav (index 1)
|
||||
hats s 2 n . ;; play hats/pedal.wav (index 2)
|
||||
```
|
||||
|
||||
The index wraps around. If you have 3 samples and request `5 n`, you get index 2 (because 5 % 3 = 2).
|
||||
|
||||
### Sample Variations with Bank
|
||||
|
||||
The `bank` parameter lets you organize variations:
|
||||
|
||||
```
|
||||
samples/
|
||||
├── kick.wav -> default
|
||||
├── kick_a.wav -> bank "a"
|
||||
├── kick_b.wav -> bank "b"
|
||||
└── kick_hard.wav -> bank "hard"
|
||||
```
|
||||
|
||||
```forth
|
||||
kick s . ;; plays kick.wav
|
||||
kick s a bank . ;; plays kick_a.wav
|
||||
kick s hard bank . ;; plays kick_hard.wav
|
||||
```
|
||||
|
||||
If the banked version does not exist, it falls back to the default.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
* **No sound**: Check output device selection.
|
||||
* Try the test sound (`t`) on Engine page).
|
||||
|
||||
* **Glitches/crackling**: Increase buffer size, restart the Engine.
|
||||
|
||||
* **High CPU**: Reduce max voices. Disable scope/spectrum. Increase buffer size.
|
||||
|
||||
* **Samples not found**: Check sample directories on Engine page. Filenames are case-sensitive on some systems.
|
||||
92
docs/engine_sources.md
Normal file
92
docs/engine_sources.md
Normal file
@@ -0,0 +1,92 @@
|
||||
# Sources
|
||||
|
||||
The audio engine provides a variety of sound sources. Use the `sound` word (or `s` for short) to select one.
|
||||
|
||||
## Basic Oscillators
|
||||
|
||||
| Name | Description |
|
||||
|------|-------------|
|
||||
| `sine` | Pure sinusoid, smooth and mellow |
|
||||
| `tri` | Triangle wave, warmer than sine, naturally band-limited |
|
||||
| `saw` | Bright sawtooth with anti-aliasing, rich in harmonics |
|
||||
| `zaw` | Raw sawtooth without anti-aliasing, lo-fi character |
|
||||
| `pulse`, `square` | Variable-width pulse wave with anti-aliasing |
|
||||
| `pulze`, `zquare` | Raw pulse without anti-aliasing, 8-bit feel |
|
||||
|
||||
`pulse` and `pulze` respond to the `pw` parameter (0.0-1.0) for pulse width. At 0.5 you get a square wave.
|
||||
|
||||
### Phase Shaping
|
||||
|
||||
All oscillators support phase shaping for timbral variation:
|
||||
|
||||
| Parameter | Range | Effect |
|
||||
|-----------|-------|--------|
|
||||
| `size` | 0-256 | Phase quantization (lo-fi, chiptune). |
|
||||
| `mult` | 0.25-16 | Phase multiplier (harmonic overtones). |
|
||||
| `warp` | -1 to 1 | Power curve asymmetry. |
|
||||
| `mirror` | 0-1 | Phase reflection point. |
|
||||
|
||||
These are super useful to get the most out of your oscillators.
|
||||
|
||||
### Sub Oscillator
|
||||
|
||||
Add a sub oscillator layer to any basic oscillator:
|
||||
|
||||
| Parameter | Range | Effect |
|
||||
|-----------|-------|--------|
|
||||
| `sub` | 0-1 | Mix level |
|
||||
| `suboct` | 1-3 | Octaves below main. |
|
||||
| `subwave` | tri/sine/square | Sub waveform. |
|
||||
|
||||
## Noise
|
||||
|
||||
| Name | Description |
|
||||
|------|-------------|
|
||||
| `white` | Equal energy across all frequencies, bright and hissy. |
|
||||
| `pink` | -3dB/octave rolloff, equal energy per octave, natural. |
|
||||
| `brown` | -6dB/octave rolloff, deep rumbling, random walk. |
|
||||
|
||||
Noise sources ignore pitch. Use filters to shape the spectrum.
|
||||
|
||||
## Live Input
|
||||
|
||||
| Name | Description |
|
||||
|------|-------------|
|
||||
| `live`, `livein`, `mic` | Live audio input from microphone or line-in |
|
||||
|
||||
All filter and effect parameters apply to the input signal.
|
||||
|
||||
## Plaits Engines
|
||||
|
||||
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`):
|
||||
|
||||
| Parameter | Controls |
|
||||
|-----------|----------|
|
||||
| `harmonics` | Harmonic content, structure, detuning. |
|
||||
| `timbre` | Brightness, tonal color. |
|
||||
| `morph` | Smooth transitions between variations. |
|
||||
|
||||
### Pitched
|
||||
|
||||
| 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.
|
||||
118
docs/engine_space.md
Normal file
118
docs/engine_space.md
Normal file
@@ -0,0 +1,118 @@
|
||||
# Space & Time
|
||||
|
||||
Spatial effects position sounds in the stereo field and add depth through delays and reverbs.
|
||||
|
||||
## Pan
|
||||
|
||||
Pan positions a sound in the stereo field.
|
||||
|
||||
```forth
|
||||
hat -0.5 pan . ( slightly left )
|
||||
perc 1 pan . ( hard right )
|
||||
kick 0 pan . ( center )
|
||||
```
|
||||
|
||||
| Parameter | Range | Description |
|
||||
|-----------|-------|-------------|
|
||||
| `pan` | -1 to 1 | Stereo position (-1 = left, 0 = center, 1 = right) |
|
||||
|
||||
## Width
|
||||
|
||||
Width controls the stereo spread using mid-side processing.
|
||||
|
||||
```forth
|
||||
pad 1.5 width . ( wider stereo )
|
||||
pad 0 width . ( mono )
|
||||
```
|
||||
|
||||
| Parameter | Range | Description |
|
||||
|-----------|-------|-------------|
|
||||
| `width` | 0+ | Stereo width (0 = mono, 1 = unchanged, 2 = exaggerated) |
|
||||
|
||||
## Haas Effect
|
||||
|
||||
The Haas effect delays one channel slightly, creating a sense of stereo width and spatial placement.
|
||||
|
||||
```forth
|
||||
snare 15 haas . ( 15ms delay on right channel )
|
||||
```
|
||||
|
||||
| Parameter | Range | Description |
|
||||
|-----------|-------|-------------|
|
||||
| `haas` | ms | Delay time (1-10ms = subtle width, 10-35ms = distinct echo) |
|
||||
|
||||
## Delay
|
||||
|
||||
Delay is a send effect that creates echoes. The `delay` parameter sets how much signal is sent to the delay bus.
|
||||
|
||||
```forth
|
||||
snare 0.3 delay 0.25 delaytime 0.5 delayfeedback .
|
||||
```
|
||||
|
||||
| Parameter | Range | Description |
|
||||
|-----------|-------|-------------|
|
||||
| `delay` | 0-1 | Send level |
|
||||
| `delaytime` | seconds | Delay time |
|
||||
| `delayfeedback` | 0-0.95 | Feedback amount |
|
||||
| `delaytype` | type | Delay algorithm |
|
||||
|
||||
### Delay Types
|
||||
|
||||
| Type | Description |
|
||||
|------|-------------|
|
||||
| `standard` | Clean digital repeats |
|
||||
| `pingpong` | Bounces between left and right |
|
||||
| `tape` | Each repeat gets darker (analog warmth) |
|
||||
| `multitap` | 4 taps with swing control via feedback |
|
||||
|
||||
```forth
|
||||
snare 0.4 delay pingpong delaytype .
|
||||
pad 0.3 delay tape delaytype .
|
||||
```
|
||||
|
||||
## Reverb
|
||||
|
||||
Reverb is a send effect that simulates acoustic spaces. The `verb` parameter sets the send level.
|
||||
|
||||
```forth
|
||||
snare 0.2 verb 2 verbdecay .
|
||||
pad 0.4 verb 4 verbdecay 0.7 verbdamp .
|
||||
```
|
||||
|
||||
| Parameter | Range | Description |
|
||||
|-----------|-------|-------------|
|
||||
| `verb` | 0-1 | Send level |
|
||||
| `verbdecay` | seconds | Reverb tail length |
|
||||
| `verbdamp` | 0-1 | High frequency damping |
|
||||
| `verbpredelay` | ms | Initial delay before reverb |
|
||||
| `verbdiff` | 0-1 | Diffusion (smears transients) |
|
||||
| `verbtype` | type | Reverb algorithm |
|
||||
|
||||
### Reverb Types
|
||||
|
||||
| Type | Description |
|
||||
|------|-------------|
|
||||
| `dattorro` | Plate reverb, bright and metallic shimmer |
|
||||
| `fdn` | Hall reverb, dense and smooth |
|
||||
|
||||
```forth
|
||||
snare 0.3 verb dattorro verbtype . ( plate )
|
||||
pad 0.5 verb fdn verbtype . ( hall )
|
||||
```
|
||||
|
||||
## Comb Filter
|
||||
|
||||
The comb filter creates resonant pitched delays, useful for Karplus-Strong string synthesis and metallic tones.
|
||||
|
||||
```forth
|
||||
white 0.5 comb 220 combfreq 0.9 combfeedback . ( plucked string )
|
||||
```
|
||||
|
||||
| Parameter | Range | Description |
|
||||
|-----------|-------|-------------|
|
||||
| `comb` | 0-1 | Send level |
|
||||
| `combfreq` | Hz | Resonant frequency |
|
||||
| `combfeedback` | 0-0.99 | Feedback (higher = longer decay) |
|
||||
| `combdamp` | 0-1 | High frequency damping |
|
||||
|
||||
Higher feedback creates longer, ringing tones. Add damping for more natural string-like decay.
|
||||
87
docs/engine_wavetable.md
Normal file
87
docs/engine_wavetable.md
Normal file
@@ -0,0 +1,87 @@
|
||||
# Wavetables
|
||||
|
||||
Any sample can be played as a wavetable. When you use the `scan` parameter, the sample automatically becomes a pitched oscillator that can morph between cycles.
|
||||
|
||||
## What is a Wavetable?
|
||||
|
||||
A wavetable is a series of single-cycle waveforms stored end-to-end in an audio file. Each cycle is a complete waveform that starts and ends at zero crossing, allowing it to loop seamlessly at any pitch.
|
||||
|
||||
```
|
||||
Sample: [cycle 0][cycle 1][cycle 2][cycle 3]
|
||||
↑ ↑
|
||||
scan 0 scan 1
|
||||
```
|
||||
|
||||
The oscillator reads through one cycle at audio rate (determining pitch), while `scan` selects which cycle to play. Values between cycles crossfade smoothly, creating timbral morphing.
|
||||
|
||||
## Basic Usage
|
||||
|
||||
Just add `scan` to any sample and it becomes a wavetable:
|
||||
|
||||
```forth
|
||||
pad 0 scan . ( play pad as wavetable, first cycle )
|
||||
pad 0.5 scan . ( blend to middle cycles )
|
||||
pad 440 freq 0 scan . ( play at A4 )
|
||||
```
|
||||
|
||||
Without `scan`, the sample plays normally. With `scan`, it becomes a looping wavetable oscillator.
|
||||
|
||||
## Parameters
|
||||
|
||||
| Parameter | Range | Description |
|
||||
|-----------|-------|-------------|
|
||||
| `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
|
||||
|
||||
The `wtlen` parameter tells the engine how many samples make up one cycle. This must match how the wavetable was created, otherwise you'll hear the wrong pitch or glitchy artifacts.
|
||||
|
||||
```forth
|
||||
pad 0 scan 2048 wtlen . ( Serum-style 2048-sample cycles )
|
||||
pad 0 scan 1024 wtlen . ( 1024-sample cycles )
|
||||
```
|
||||
|
||||
Common cycle lengths are powers of two: 256, 512, 1024, 2048. Serum uses 2048 samples per cycle. The number of cycles in a wavetable is the total sample length divided by `wtlen`. If `wtlen` is 0 (default), the entire sample is treated as one cycle. The sample still loops as a pitched oscillator, but `scan` has no morphing effect since there's only one cycle.
|
||||
|
||||
## Scanning
|
||||
|
||||
The `scan` parameter selects which cycle to play:
|
||||
|
||||
```forth
|
||||
pad 0 scan . ( first cycle only )
|
||||
pad 0.5 scan . ( blend between middle cycles )
|
||||
pad 1 scan . ( last cycle only )
|
||||
```
|
||||
|
||||
## LFO Modulation
|
||||
|
||||
Automate the scan position with a built-in LFO:
|
||||
|
||||
```forth
|
||||
pad 0 scan 2 scanlfo 0.3 scandepth . ( 2 Hz modulation, 30% depth )
|
||||
```
|
||||
|
||||
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:
|
||||
|
||||
- Contains multiple single-cycle waveforms of identical length
|
||||
- Each cycle starts and ends at zero crossing for seamless looping
|
||||
- Uses power-of-two cycle lengths (256, 512, 1024, 2048)
|
||||
- Has cycles that morph smoothly from one to the next
|
||||
|
||||
You can find wavetable packs online or create your own in tools like Serum, WaveEdit, or Audacity (using zero-crossing snap). Single-cycle waveforms also work. With `wtlen` set to 0, a single-cycle sample becomes a basic pitched oscillator.
|
||||
43
docs/engine_words.md
Normal file
43
docs/engine_words.md
Normal file
@@ -0,0 +1,43 @@
|
||||
# Words & Sounds
|
||||
|
||||
Word definitions let you abstract sound design into reusable units.
|
||||
|
||||
## Defining Sounds
|
||||
|
||||
```forth
|
||||
: lead "saw" s 0.3 gain 1200 lpf ;
|
||||
```
|
||||
|
||||
Use it with different notes:
|
||||
|
||||
```forth
|
||||
c4 note lead .
|
||||
e4 note lead .
|
||||
```
|
||||
|
||||
## Self-Contained Words
|
||||
|
||||
Include the emit to make the word play directly:
|
||||
|
||||
```forth
|
||||
: kk "kick" s 1 decay . ;
|
||||
: hh "hihat" s 0.5 gain 0.5 decay . ;
|
||||
```
|
||||
|
||||
Steps become simple:
|
||||
|
||||
```forth
|
||||
kk
|
||||
0.5 at hh
|
||||
```
|
||||
|
||||
## Effect Presets
|
||||
|
||||
```forth
|
||||
: dark 800 lpf 0.6 lpq ;
|
||||
: wet 0.7 verb 8 verbdiff ;
|
||||
```
|
||||
|
||||
```forth
|
||||
c4 note saw s dark wet .
|
||||
```
|
||||
@@ -1,2 +0,0 @@
|
||||
# Envelopes
|
||||
|
||||
@@ -1,65 +0,0 @@
|
||||
# EQ & Stereo
|
||||
|
||||
Shape the frequency balance and stereo image of your sounds.
|
||||
|
||||
## Three-Band EQ
|
||||
|
||||
```forth
|
||||
"mix" s
|
||||
3 eqlo ( boost low shelf by 3dB )
|
||||
-2 eqmid ( cut mids by 2dB )
|
||||
1 eqhi ( boost high shelf by 1dB )
|
||||
.
|
||||
```
|
||||
|
||||
## Tilt EQ
|
||||
|
||||
A simple one-knob EQ that tilts the frequency balance:
|
||||
|
||||
```forth
|
||||
"bright" s 0.5 tilt . ( brighter )
|
||||
"dark" s -0.5 tilt . ( darker )
|
||||
```
|
||||
|
||||
Range: -1 (dark) to 1 (bright)
|
||||
|
||||
## Gain and Pan
|
||||
|
||||
```forth
|
||||
"kick" s 0.8 gain . ( volume 0-1 )
|
||||
"hat" s 0.5 pan . ( pan right )
|
||||
"hat" s -0.5 pan . ( pan left )
|
||||
"snare" s 1.2 postgain . ( post-processing gain )
|
||||
"lead" s 100 velocity . ( velocity/dynamics )
|
||||
```
|
||||
|
||||
## Stereo Width
|
||||
|
||||
```forth
|
||||
"pad" s 0 width . ( mono )
|
||||
"pad" s 1 width . ( normal stereo )
|
||||
"pad" s 2 width . ( extra wide )
|
||||
```
|
||||
|
||||
## Haas Effect
|
||||
|
||||
Create spatial positioning with subtle delay between channels:
|
||||
|
||||
```forth
|
||||
"vocal" s 8 haas . ( 8ms delay for spatial effect )
|
||||
```
|
||||
|
||||
## Words
|
||||
|
||||
| Word | Stack | Description |
|
||||
|------|-------|-------------|
|
||||
| `eqlo` | (f --) | Set low shelf gain (dB) |
|
||||
| `eqmid` | (f --) | Set mid peak gain (dB) |
|
||||
| `eqhi` | (f --) | Set high shelf gain (dB) |
|
||||
| `tilt` | (f --) | Set tilt EQ (-1 dark, 1 bright) |
|
||||
| `gain` | (f --) | Set volume (0-1) |
|
||||
| `postgain` | (f --) | Set post gain |
|
||||
| `velocity` | (f --) | Set velocity |
|
||||
| `pan` | (f --) | Set pan (-1 to 1) |
|
||||
| `width` | (f --) | Set stereo width |
|
||||
| `haas` | (f --) | Set Haas delay (ms) |
|
||||
@@ -1,2 +0,0 @@
|
||||
# Filters
|
||||
|
||||
@@ -1,65 +0,0 @@
|
||||
# Generators
|
||||
|
||||
Create sequences of values on the stack.
|
||||
|
||||
## Arithmetic Range
|
||||
|
||||
The `..` operator pushes an arithmetic sequence:
|
||||
|
||||
```forth
|
||||
1 4 .. ( pushes 1 2 3 4 )
|
||||
60 67 .. ( pushes 60 61 62 63 64 65 66 67 )
|
||||
```
|
||||
|
||||
## Geometric Range
|
||||
|
||||
The `geom..` operator creates a geometric sequence:
|
||||
|
||||
```forth
|
||||
1 2 4 geom.. ( pushes 1 2 4 8 )
|
||||
100 0.5 4 geom.. ( pushes 100 50 25 12.5 )
|
||||
```
|
||||
|
||||
Stack: `(start ratio count -- values...)`
|
||||
|
||||
## Generate
|
||||
|
||||
The `gen` word executes a quotation multiple times:
|
||||
|
||||
```forth
|
||||
{ 1 6 rand } 4 gen ( 4 random values from 1-6 )
|
||||
{ coin } 8 gen ( 8 random 0/1 values )
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
Random melody:
|
||||
|
||||
```forth
|
||||
"pluck" s
|
||||
{ 48 72 rand } 4 gen 4 choose note
|
||||
.
|
||||
```
|
||||
|
||||
Chord from range:
|
||||
|
||||
```forth
|
||||
"pad" s
|
||||
60 67 .. 8 choose note ( random note in octave )
|
||||
.
|
||||
```
|
||||
|
||||
Euclidean-style rhythm values:
|
||||
|
||||
```forth
|
||||
0 1 0 0 1 0 1 0 ( manual )
|
||||
{ coin } 8 gen ( random 8-step pattern )
|
||||
```
|
||||
|
||||
## Words
|
||||
|
||||
| Word | Stack | Description |
|
||||
|------|-------|-------------|
|
||||
| `..` | (start end -- values...) | Arithmetic sequence |
|
||||
| `geom..` | (start ratio count -- values...) | Geometric sequence |
|
||||
| `gen` | (quot n -- results...) | Execute quotation n times |
|
||||
@@ -1,63 +0,0 @@
|
||||
# Keybindings
|
||||
|
||||
## Navigation
|
||||
|
||||
- **Ctrl+Left/Right**: Switch between pages (Main, Audio, Doc)
|
||||
- **q**: Quit (with confirmation)
|
||||
|
||||
## Main Page - Sequencer Focus
|
||||
|
||||
- **Arrow keys**: Navigate steps in pattern
|
||||
- **Enter**: Toggle step active/inactive
|
||||
- **Tab**: Switch focus to editor
|
||||
- **Space**: Play/pause
|
||||
|
||||
### Pattern Controls
|
||||
|
||||
- **< / >**: Decrease/increase pattern length
|
||||
- **[ / ]**: Decrease/increase pattern speed
|
||||
- **p**: Open pattern picker
|
||||
- **b**: Open bank picker
|
||||
|
||||
### Slots
|
||||
|
||||
- **1-8**: Toggle slot on/off
|
||||
- **g**: Queue current pattern to first free slot
|
||||
- **G**: Queue removal of current pattern from its slot
|
||||
|
||||
### Files
|
||||
|
||||
- **s**: Save project
|
||||
- **l**: Load project
|
||||
- **Ctrl+C**: Copy step script
|
||||
- **Ctrl+V**: Paste step script
|
||||
|
||||
### Execution
|
||||
|
||||
- **Ctrl+R**: Run current step's script immediately (one-shot)
|
||||
|
||||
### Tempo
|
||||
|
||||
- **+ / =**: Increase tempo
|
||||
- **-**: Decrease tempo
|
||||
|
||||
## Main Page - Editor Focus
|
||||
|
||||
- **Tab / Esc**: Return to sequencer focus
|
||||
- **Ctrl+E**: Compile current step script
|
||||
- **Ctrl+R**: Run script in editor immediately (one-shot)
|
||||
|
||||
## Audio Page
|
||||
|
||||
- **h**: Hush (stop all sounds gracefully)
|
||||
- **p**: Panic (kill all sounds immediately)
|
||||
- **r**: Reset peak voice counter
|
||||
- **t**: Test sound (plays 440Hz sine)
|
||||
- **Space**: Play/pause
|
||||
|
||||
## Doc Page
|
||||
|
||||
- **j / Down**: Next topic
|
||||
- **k / Up**: Previous topic
|
||||
- **PgDn**: Scroll content down
|
||||
- **PgUp**: Scroll content up
|
||||
@@ -1,47 +0,0 @@
|
||||
# Ladder Filters
|
||||
|
||||
Ladder filters provide a classic analog-style filter sound with stronger resonance character than the standard SVF filters.
|
||||
|
||||
## Ladder Lowpass
|
||||
|
||||
```forth
|
||||
"saw" s 2000 llpf . ( ladder lowpass frequency )
|
||||
"saw" s 0.7 llpq . ( ladder lowpass resonance )
|
||||
```
|
||||
|
||||
## Ladder Highpass
|
||||
|
||||
```forth
|
||||
"noise" s 500 lhpf . ( ladder highpass frequency )
|
||||
"noise" s 0.5 lhpq . ( ladder highpass resonance )
|
||||
```
|
||||
|
||||
## Ladder Bandpass
|
||||
|
||||
```forth
|
||||
"pad" s 1000 lbpf . ( ladder bandpass frequency )
|
||||
"pad" s 0.6 lbpq . ( ladder bandpass resonance )
|
||||
```
|
||||
|
||||
## Comparison with SVF
|
||||
|
||||
Ladder filters have a different resonance character:
|
||||
- More aggressive self-oscillation at high resonance
|
||||
- Classic "squelchy" acid sound
|
||||
- 24dB/octave slope
|
||||
|
||||
Standard SVF filters (`lpf`, `hpf`, `bpf`) have:
|
||||
- Cleaner resonance
|
||||
- Full envelope control (attack, decay, sustain, release)
|
||||
- 12dB/octave slope
|
||||
|
||||
## Words
|
||||
|
||||
| Word | Stack | Description |
|
||||
|------|-------|-------------|
|
||||
| `llpf` | (f --) | Set ladder lowpass frequency |
|
||||
| `llpq` | (f --) | Set ladder lowpass resonance |
|
||||
| `lhpf` | (f --) | Set ladder highpass frequency |
|
||||
| `lhpq` | (f --) | Set ladder highpass resonance |
|
||||
| `lbpf` | (f --) | Set ladder bandpass frequency |
|
||||
| `lbpq` | (f --) | Set ladder bandpass resonance |
|
||||
74
docs/lfo.md
74
docs/lfo.md
@@ -1,74 +0,0 @@
|
||||
# LFO & Ramps
|
||||
|
||||
Generate time-varying values synchronized to the beat for modulation.
|
||||
|
||||
## Ramp
|
||||
|
||||
The `ramp` word creates a sawtooth wave from 0 to 1:
|
||||
|
||||
```forth
|
||||
0.25 1.0 ramp ( one cycle per 4 beats, linear )
|
||||
1.0 2.0 ramp ( one cycle per beat, exponential curve )
|
||||
```
|
||||
|
||||
Stack: `(freq curve -- val)`
|
||||
|
||||
## Shortcut Ramps
|
||||
|
||||
```forth
|
||||
0.5 linramp ( linear ramp, curve=1 )
|
||||
0.25 expramp ( exponential ramp, curve=3 )
|
||||
2.0 logramp ( logarithmic ramp, curve=0.3 )
|
||||
```
|
||||
|
||||
## Triangle Wave
|
||||
|
||||
```forth
|
||||
0.5 tri ( triangle wave 0→1→0 )
|
||||
```
|
||||
|
||||
## Perlin Noise
|
||||
|
||||
Smooth random modulation:
|
||||
|
||||
```forth
|
||||
0.25 perlin ( smooth noise at 0.25x beat rate )
|
||||
```
|
||||
|
||||
## Range Scaling
|
||||
|
||||
Scale a 0-1 value to any range:
|
||||
|
||||
```forth
|
||||
0.5 200 800 range ( 0.5 becomes 500 )
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
Modulate filter cutoff:
|
||||
|
||||
```forth
|
||||
"saw" s
|
||||
0.25 1.0 ramp 500 2000 range lpf
|
||||
.
|
||||
```
|
||||
|
||||
Random panning:
|
||||
|
||||
```forth
|
||||
"hat" s
|
||||
0.5 perlin -1 1 range pan
|
||||
.
|
||||
```
|
||||
|
||||
## Words
|
||||
|
||||
| Word | Stack | Description |
|
||||
|------|-------|-------------|
|
||||
| `ramp` | (freq curve -- val) | Ramp 0-1: fract(freq*beat)^curve |
|
||||
| `linramp` | (freq -- val) | Linear ramp (curve=1) |
|
||||
| `expramp` | (freq -- val) | Exponential ramp (curve=3) |
|
||||
| `logramp` | (freq -- val) | Logarithmic ramp (curve=0.3) |
|
||||
| `tri` | (freq -- val) | Triangle wave 0→1→0 |
|
||||
| `perlin` | (freq -- val) | Perlin noise 0-1 |
|
||||
| `range` | (val min max -- scaled) | Scale 0-1 to min-max |
|
||||
@@ -1,2 +0,0 @@
|
||||
# Ableton Link
|
||||
|
||||
58
docs/lofi.md
58
docs/lofi.md
@@ -1,58 +0,0 @@
|
||||
# Lo-fi Effects
|
||||
|
||||
Add grit, warmth, and character with bit crushing, folding, and distortion.
|
||||
|
||||
## Bit Crush
|
||||
|
||||
Reduce bit depth for digital artifacts:
|
||||
|
||||
```forth
|
||||
"drum" s 8 crush . ( 8-bit crush )
|
||||
"drum" s 4 crush . ( heavy 4-bit crush )
|
||||
```
|
||||
|
||||
## Wave Folding
|
||||
|
||||
Fold the waveform back on itself for complex harmonics:
|
||||
|
||||
```forth
|
||||
"sine" s 2 fold . ( moderate folding )
|
||||
"sine" s 4 fold . ( aggressive folding )
|
||||
```
|
||||
|
||||
## Wave Wrap
|
||||
|
||||
Wrap the waveform for a different flavor of distortion:
|
||||
|
||||
```forth
|
||||
"bass" s 0.5 wrap .
|
||||
```
|
||||
|
||||
## Distortion
|
||||
|
||||
Classic overdrive/distortion:
|
||||
|
||||
```forth
|
||||
"guitar" s 0.5 distort . ( distortion amount )
|
||||
"guitar" s 0.8 distortvol . ( output volume )
|
||||
```
|
||||
|
||||
## Combining Effects
|
||||
|
||||
```forth
|
||||
"drum" s
|
||||
12 crush
|
||||
1.5 fold
|
||||
0.3 distort
|
||||
.
|
||||
```
|
||||
|
||||
## Words
|
||||
|
||||
| Word | Stack | Description |
|
||||
|------|-------|-------------|
|
||||
| `crush` | (f --) | Set bit crush depth |
|
||||
| `fold` | (f --) | Set wave fold amount |
|
||||
| `wrap` | (f --) | Set wave wrap amount |
|
||||
| `distort` | (f --) | Set distortion amount |
|
||||
| `distortvol` | (f --) | Set distortion output volume |
|
||||
@@ -1,89 +0,0 @@
|
||||
# Logic
|
||||
|
||||
Boolean operations and conditional execution.
|
||||
|
||||
## Boolean Operators
|
||||
|
||||
```forth
|
||||
1 1 and ( 1 )
|
||||
0 1 or ( 1 )
|
||||
1 not ( 0 )
|
||||
1 0 xor ( 1 )
|
||||
1 1 nand ( 0 )
|
||||
0 0 nor ( 1 )
|
||||
```
|
||||
|
||||
## Conditional Execution
|
||||
|
||||
Execute a quotation if condition is true:
|
||||
|
||||
```forth
|
||||
{ "kick" s . } coin ? ( 50% chance )
|
||||
{ 0.5 gain } step 0 = ? ( only on step 0 )
|
||||
```
|
||||
|
||||
Execute if false:
|
||||
|
||||
```forth
|
||||
{ "snare" s . } coin !? ( if NOT coin )
|
||||
```
|
||||
|
||||
## If-Else
|
||||
|
||||
```forth
|
||||
{ "kick" s } { "snare" s } coin ifelse .
|
||||
```
|
||||
|
||||
Stack: `(true-quot false-quot bool --)`
|
||||
|
||||
## Pick
|
||||
|
||||
Choose from multiple quotations:
|
||||
|
||||
```forth
|
||||
{ 60 } { 64 } { 67 } step 3 mod pick note
|
||||
```
|
||||
|
||||
Stack: `(quot1 quot2 ... quotN n -- result)`
|
||||
|
||||
## Apply
|
||||
|
||||
Execute a quotation unconditionally:
|
||||
|
||||
```forth
|
||||
{ 2 * } apply ( doubles top of stack )
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
Conditional parameter:
|
||||
|
||||
```forth
|
||||
"kick" s
|
||||
{ 0.8 } { 0.4 } iter 2 mod = ifelse gain
|
||||
.
|
||||
```
|
||||
|
||||
Multi-way branching:
|
||||
|
||||
```forth
|
||||
"synth" s
|
||||
{ c4 } { e4 } { g4 } { c5 } step 4 mod pick note
|
||||
.
|
||||
```
|
||||
|
||||
## Words
|
||||
|
||||
| Word | Stack | Description |
|
||||
|------|-------|-------------|
|
||||
| `and` | (a b -- bool) | Logical and |
|
||||
| `or` | (a b -- bool) | Logical or |
|
||||
| `not` | (a -- bool) | Logical not |
|
||||
| `xor` | (a b -- bool) | Exclusive or |
|
||||
| `nand` | (a b -- bool) | Not and |
|
||||
| `nor` | (a b -- bool) | Not or |
|
||||
| `?` | (quot bool --) | Execute if true |
|
||||
| `!?` | (quot bool --) | Execute if false |
|
||||
| `ifelse` | (t-quot f-quot bool --) | If-else |
|
||||
| `pick` | (..quots n --) | Execute nth quotation |
|
||||
| `apply` | (quot --) | Execute unconditionally |
|
||||
68
docs/midi_input.md
Normal file
68
docs/midi_input.md
Normal file
@@ -0,0 +1,68 @@
|
||||
# MIDI Input
|
||||
|
||||
Read incoming MIDI control change values with the `ccval` word. This lets you use hardware controllers to modulate parameters in your scripts.
|
||||
|
||||
## Reading CC Values
|
||||
|
||||
The `ccval` word takes a CC number and channel from the stack, and returns the last received value:
|
||||
|
||||
```forth
|
||||
1 1 ccval ;; read CC 1 (mod wheel) on channel 1
|
||||
```
|
||||
|
||||
Stack effect: `(cc chan -- val)`
|
||||
|
||||
The returned value is `0`-`127`. If no message has been received for that CC/channel combination, the value is `0`.
|
||||
|
||||
## Device Selection
|
||||
|
||||
Use `dev` to select which input device slot to read from:
|
||||
|
||||
```forth
|
||||
1 dev 1 1 ccval ;; read from device slot 1
|
||||
```
|
||||
|
||||
Device defaults to `0` if not specified.
|
||||
|
||||
## Practical Examples
|
||||
|
||||
Map a controller knob to filter cutoff:
|
||||
|
||||
```forth
|
||||
74 1 ccval 127 / 200 2740 range lpf
|
||||
```
|
||||
|
||||
Use mod wheel for vibrato depth:
|
||||
|
||||
```forth
|
||||
1 1 ccval 127 / 0 0.5 range vibdepth
|
||||
```
|
||||
|
||||
Crossfade between two sounds:
|
||||
|
||||
```forth
|
||||
1 1 ccval 127 / ;; normalize to 0.0-1.0
|
||||
dup saw s swap gain .
|
||||
1 swap - tri s swap gain .
|
||||
```
|
||||
|
||||
## Scaling Values
|
||||
|
||||
CC values are integers `0`-`127`. Normalize to `0.0`-`1.0` first, then use `range` to scale:
|
||||
|
||||
```forth
|
||||
;; normalize to 0.0-1.0
|
||||
74 1 ccval 127 /
|
||||
|
||||
;; scale to custom range (e.g., 200-4000)
|
||||
74 1 ccval 127 / 200 4000 range
|
||||
|
||||
;; bipolar range (-1.0 to 1.0)
|
||||
74 1 ccval 127 / -1 1 range
|
||||
```
|
||||
|
||||
The `range` word takes a normalized value (`0.0`-`1.0`) and scales it to your target range: `(val min max -- scaled)`.
|
||||
|
||||
## Latency
|
||||
|
||||
CC values are sampled at the start of each step. Changes during a step take effect on the next step. For smoothest results, turn knobs slowly or use higher step rates.
|
||||
26
docs/midi_intro.md
Normal file
26
docs/midi_intro.md
Normal file
@@ -0,0 +1,26 @@
|
||||
# MIDI
|
||||
|
||||
Cagire speaks MIDI. You can send notes, control changes, and other messages to external synthesizers, drum machines, and DAWs. You can also read incoming control change values from MIDI controllers and use them to modulate your scripts.
|
||||
|
||||
## Device Slots
|
||||
|
||||
Cagire provides four input slots and four output slots, numbered `0` through `3`. Each slot can connect to one MIDI device. By default, slot `0` is used for both input and output.
|
||||
|
||||
## Configuration
|
||||
|
||||
Configure your MIDI devices in the **Options** view. Select input and output devices for each slot. Changes take effect immediately.
|
||||
|
||||
## MIDI vs Audio
|
||||
|
||||
The audio engine (`Doux`) and MIDI are independent systems. Use `.` to emit audio commands, use `m.` to emit MIDI messages. You can use both in the same script:
|
||||
|
||||
```forth
|
||||
saw s c4 note 0.5 gain . ;; audio
|
||||
60 note 100 velocity m. ;; MIDI
|
||||
```
|
||||
|
||||
MIDI is useful when you want to sequence external gear, layer Cagire with hardware synths, or integrate into a larger studio setup. The audio engine is self-contained and needs no external equipment.
|
||||
|
||||
## Clock and Transport
|
||||
|
||||
Cagire can send MIDI clock and transport messages to synchronize external gear. Use `mclock` to send a single clock pulse, and `mstart`, `mstop`, `mcont` for transport control. MIDI clock requires 24 pulses per quarter note, so you need to call `mclock` at the appropriate rate for your tempo.
|
||||
92
docs/midi_output.md
Normal file
92
docs/midi_output.md
Normal file
@@ -0,0 +1,92 @@
|
||||
# MIDI Output
|
||||
|
||||
Send MIDI messages using the `m.` word. Build up parameters on the stack, then emit. The system determines message type based on which parameters you set.
|
||||
|
||||
## Note Messages
|
||||
|
||||
The default message type is a note. Set `note` and `velocity`, then emit:
|
||||
|
||||
```forth
|
||||
60 note 100 velocity m. ;; middle C, velocity 100
|
||||
c4 note 80 velocity m. ;; same pitch, lower velocity
|
||||
```
|
||||
|
||||
| Parameter | Stack | Range | Description |
|
||||
|-----------|-------|-------|-------------|
|
||||
| `note` | `(n --)` | 0-127 | MIDI note number |
|
||||
| `velocity` | `(n --)` | 0-127 | Note velocity |
|
||||
| `chan` | `(n --)` | 1-16 | MIDI channel |
|
||||
| `dur` | `(f --)` | steps | Note duration |
|
||||
| `dev` | `(n --)` | 0-3 | Output device slot |
|
||||
|
||||
Duration (`dur`) is measured in steps. If not set, the note plays until the next step. Channel defaults to `1`, device defaults to `0`.
|
||||
|
||||
## Control Change
|
||||
|
||||
Set both `ccnum` (controller number) and `ccout` (value) to send a CC message:
|
||||
|
||||
```forth
|
||||
74 ccnum 64 ccout m. ;; CC 74, value 64
|
||||
1 ccnum 127 ccout m. ;; mod wheel full
|
||||
```
|
||||
|
||||
| Parameter | Stack | Range | Description |
|
||||
|-----------|-------|-------|-------------|
|
||||
| `ccnum` | `(n --)` | 0-127 | Controller number |
|
||||
| `ccout` | `(n --)` | 0-127 | Controller value |
|
||||
|
||||
## Pitch Bend
|
||||
|
||||
Set `bend` to send pitch bend. The range is `-1.0` (full down) to `1.0` (full up), with `0.0` as center:
|
||||
|
||||
```forth
|
||||
0.5 bend m. ;; bend up halfway
|
||||
-1.0 bend m. ;; full bend down
|
||||
```
|
||||
|
||||
## Channel Pressure
|
||||
|
||||
Set `pressure` to send channel aftertouch:
|
||||
|
||||
```forth
|
||||
64 pressure m. ;; medium pressure
|
||||
```
|
||||
|
||||
## Program Change
|
||||
|
||||
Set `program` to send a program change message:
|
||||
|
||||
```forth
|
||||
0 program m. ;; select program 0
|
||||
127 program m. ;; select program 127
|
||||
```
|
||||
|
||||
## Message Priority
|
||||
|
||||
When multiple message types are set, only one is sent per emit. Priority order:
|
||||
|
||||
1. Control Change (if `ccnum` AND `ccout` set)
|
||||
2. Pitch Bend
|
||||
3. Channel Pressure
|
||||
4. Program Change
|
||||
5. Note (default)
|
||||
|
||||
To send multiple message types, use multiple emits:
|
||||
|
||||
```forth
|
||||
74 ccnum 100 ccout m. ;; CC first
|
||||
60 note 100 velocity m. ;; then note
|
||||
```
|
||||
|
||||
## Real-Time Messages
|
||||
|
||||
Transport and clock messages for external synchronization:
|
||||
|
||||
| Word | Description |
|
||||
|------|-------------|
|
||||
| `mclock` | Send MIDI clock pulse |
|
||||
| `mstart` | Send MIDI start |
|
||||
| `mstop` | Send MIDI stop |
|
||||
| `mcont` | Send MIDI continue |
|
||||
|
||||
These ignore all parameters and send immediately.
|
||||
@@ -1,61 +0,0 @@
|
||||
# Modulation Effects
|
||||
|
||||
Phaser, flanger, and chorus effects for movement and width.
|
||||
|
||||
## Phaser
|
||||
|
||||
```forth
|
||||
"pad" s
|
||||
1 phaser ( rate in Hz )
|
||||
0.5 phaserdepth ( depth )
|
||||
0.5 phasersweep ( sweep range )
|
||||
1000 phasercenter ( center frequency )
|
||||
.
|
||||
```
|
||||
|
||||
## Flanger
|
||||
|
||||
```forth
|
||||
"guitar" s
|
||||
0.5 flanger ( rate )
|
||||
0.5 flangerdepth ( depth )
|
||||
0.5 flangerfeedback ( feedback for metallic sound )
|
||||
.
|
||||
```
|
||||
|
||||
## Chorus
|
||||
|
||||
```forth
|
||||
"keys" s
|
||||
1 chorus ( rate )
|
||||
0.5 chorusdepth ( depth )
|
||||
0.02 chorusdelay ( base delay time )
|
||||
.
|
||||
```
|
||||
|
||||
## Combining Effects
|
||||
|
||||
```forth
|
||||
"pad" s
|
||||
c4 note
|
||||
0.3 phaserdepth
|
||||
0.5 phaser
|
||||
0.2 chorus
|
||||
0.3 chorusdepth
|
||||
.
|
||||
```
|
||||
|
||||
## Words
|
||||
|
||||
| Word | Stack | Description |
|
||||
|------|-------|-------------|
|
||||
| `phaser` | (f --) | Set phaser rate |
|
||||
| `phaserdepth` | (f --) | Set phaser depth |
|
||||
| `phasersweep` | (f --) | Set phaser sweep |
|
||||
| `phasercenter` | (f --) | Set phaser center frequency |
|
||||
| `flanger` | (f --) | Set flanger rate |
|
||||
| `flangerdepth` | (f --) | Set flanger depth |
|
||||
| `flangerfeedback` | (f --) | Set flanger feedback |
|
||||
| `chorus` | (f --) | Set chorus rate |
|
||||
| `chorusdepth` | (f --) | Set chorus depth |
|
||||
| `chorusdelay` | (f --) | Set chorus delay |
|
||||
@@ -1,2 +0,0 @@
|
||||
# Modulation
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
# Notes & MIDI
|
||||
|
||||
214
docs/oddities.md
Normal file
214
docs/oddities.md
Normal file
@@ -0,0 +1,214 @@
|
||||
# Oddities
|
||||
|
||||
Cagire's Forth is not a classic Forth. It borrows the core ideas (stack-based evaluation, postfix notation, word definitions) but adds modern features and domain-specific extensions. If you know traditional Forth, here are the differences.
|
||||
|
||||
## Comments
|
||||
|
||||
Classic Forth uses parentheses for comments:
|
||||
|
||||
```forth
|
||||
( this is a comment )
|
||||
```
|
||||
|
||||
Cagire uses double semicolons:
|
||||
|
||||
```forth
|
||||
;; this is a comment
|
||||
```
|
||||
|
||||
Everything after `;;` until the end of the line is ignored.
|
||||
|
||||
## Quotations
|
||||
|
||||
Classic Forth has no quotations. Code is not a value you can pass around.
|
||||
|
||||
Cagire has first-class quotations using curly braces:
|
||||
|
||||
```forth
|
||||
{ dup + }
|
||||
```
|
||||
|
||||
This pushes a block of code onto the stack. You can store it, pass it to other words, and execute it later. Quotations enable conditionals, probability, and cycling.
|
||||
|
||||
## Conditionals
|
||||
|
||||
Classic Forth uses `IF ... ELSE ... THEN`:
|
||||
|
||||
```forth
|
||||
x 0 > IF 1 ELSE -1 THEN
|
||||
```
|
||||
|
||||
Cagire supports this syntax but also provides quotation-based conditionals:
|
||||
|
||||
```forth
|
||||
{ 1 } { -1 } x 0 > ifelse
|
||||
```
|
||||
|
||||
The words `?` and `!?` execute a quotation based on a condition:
|
||||
|
||||
```forth
|
||||
{ "kick" s . } coin ? ;; execute if coin is 1
|
||||
{ "snare" s . } coin !? ;; execute if coin is 0
|
||||
```
|
||||
|
||||
## Strings
|
||||
|
||||
Classic Forth has limited string support. You print strings with `."`:
|
||||
|
||||
```forth
|
||||
." Hello World"
|
||||
```
|
||||
|
||||
Cagire has first-class strings:
|
||||
|
||||
```forth
|
||||
"hello"
|
||||
```
|
||||
|
||||
This pushes a string value onto the stack. Strings are used for sound names, sample names, and variable keys. You often do not need quotes at all. Any unrecognized word becomes a string automatically:
|
||||
|
||||
```forth
|
||||
kick s . ;; "kick" is not a word, so it becomes the string "kick"
|
||||
myweirdname ;; pushes "myweirdname" onto the stack
|
||||
```
|
||||
|
||||
This makes scripts cleaner. You only need quotes when the string contains spaces or conflicts with a real word.
|
||||
|
||||
## Variables
|
||||
|
||||
Classic Forth declares variables explicitly:
|
||||
|
||||
```forth
|
||||
VARIABLE x
|
||||
10 x !
|
||||
x @
|
||||
```
|
||||
|
||||
Cagire uses prefix syntax:
|
||||
|
||||
```forth
|
||||
10 !x ;; store 10 in x
|
||||
@x ;; fetch x (returns 0 if undefined)
|
||||
```
|
||||
|
||||
No declaration needed. Variables spring into existence when you store to them.
|
||||
|
||||
## Floating Point
|
||||
|
||||
Classic Forth (in its original form) has no floating point. Numbers are integers. Floating point was added later as an optional extension with separate words. Cagire has native floating point:
|
||||
|
||||
```forth
|
||||
3.14159
|
||||
0.5 0.3 + ;; 0.8
|
||||
```
|
||||
|
||||
Integers and floats mix freely. Division always produces a float.
|
||||
|
||||
## Loops
|
||||
|
||||
Classic Forth has `DO ... LOOP`:
|
||||
|
||||
```forth
|
||||
10 0 DO I . LOOP
|
||||
```
|
||||
|
||||
Cagire uses a quotation-based loop with `times`:
|
||||
|
||||
```forth
|
||||
4 { @i . } times ;; prints 0 1 2 3
|
||||
```
|
||||
|
||||
The loop counter is stored in the variable `i`, accessed with `@i`. This fits Cagire's style where control flow uses quotations.
|
||||
|
||||
```forth
|
||||
4 { @i 4 / at hat s . } times ;; hat at 0, 0.25, 0.5, 0.75
|
||||
4 { c4 @i + note sine s . } times ;; ascending notes
|
||||
```
|
||||
|
||||
For generating sequences without side effects, use `..` or `gen`:
|
||||
|
||||
```forth
|
||||
1 5 .. ;; pushes 1 2 3 4 5
|
||||
{ dup * } 4 gen ;; pushes 0 1 4 9 (squares)
|
||||
```
|
||||
|
||||
## The Command Register
|
||||
|
||||
This is completely unique to Cagire. Traditional Forth programs print text. Cagire programs build sound commands.
|
||||
|
||||
The command register accumulates a sound name and parameters:
|
||||
|
||||
```forth
|
||||
"sine" sound ;; set sound
|
||||
440 freq ;; add parameter
|
||||
0.5 gain ;; add parameter
|
||||
. ;; emit and clear
|
||||
```
|
||||
|
||||
Nothing is sent to the audio engine until you emit with `.`. This is unlike any classic Forth.
|
||||
|
||||
## Context Words
|
||||
|
||||
Cagire provides words that read the current sequencer state:
|
||||
|
||||
```forth
|
||||
step ;; current step index (0-127)
|
||||
beat ;; current beat position
|
||||
iter ;; pattern iteration count
|
||||
tempo ;; current BPM
|
||||
phase ;; position in bar (0-1)
|
||||
```
|
||||
|
||||
These have no equivalent in classic Forth. They connect your script to the sequencer's timeline.
|
||||
|
||||
## Probability
|
||||
|
||||
Classic Forth is deterministic. Cagire has built-in randomness:
|
||||
|
||||
```forth
|
||||
{ "snare" s . } 50 prob ;; 50% chance
|
||||
{ "clap" s . } 0.25 chance ;; 25% chance
|
||||
{ "hat" s . } often ;; 75% chance
|
||||
{ "rim" s . } sometimes ;; 50% chance
|
||||
{ "tom" s . } rarely ;; 25% chance
|
||||
```
|
||||
|
||||
These words take a quotation and execute it probabilistically.
|
||||
|
||||
## Cycling
|
||||
|
||||
Cagire has built-in support for cycling through values. Push values onto the stack, then select one based on pattern state:
|
||||
|
||||
```forth
|
||||
60 64 67 3 cycle note
|
||||
```
|
||||
|
||||
Each time the step runs, a different note is selected. The `3` tells `cycle` how many values to pick from.
|
||||
|
||||
You can also use quotations if you need to execute code:
|
||||
|
||||
```forth
|
||||
{ c4 note } { e4 note } { g4 note } 3 cycle
|
||||
```
|
||||
|
||||
When the selected value is a quotation, it gets executed. When it is a plain value, it gets pushed onto the stack.
|
||||
|
||||
Three cycling words exist:
|
||||
|
||||
- `cycle` - selects based on `runs` (how many times this step has played)
|
||||
- `pcycle` - selects based on `iter` (how many times the pattern has looped)
|
||||
- `tcycle` - creates a cycle list that resolves at emit time
|
||||
|
||||
The difference between `cycle` and `pcycle` matters when patterns have different lengths. `cycle` counts per-step, `pcycle` counts per-pattern.
|
||||
|
||||
`tcycle` is special. It does not select immediately. Instead it creates a value that cycles when emitted:
|
||||
|
||||
```forth
|
||||
0.3 0.5 0.7 3 tcycle gain
|
||||
```
|
||||
|
||||
If you emit multiple times in one step (using `at`), each emit gets the next value from the cycle.
|
||||
|
||||
## Summary
|
||||
|
||||
Cagire's Forth is a domain-specific language for music. It keeps Forth's elegance (stack, postfix, definitions) but adapts it for live coding.
|
||||
@@ -1,75 +0,0 @@
|
||||
# Oscillators
|
||||
|
||||
Control synthesis oscillator parameters for synth sounds.
|
||||
|
||||
## Frequency and Pitch
|
||||
|
||||
```forth
|
||||
"sine" s 440 freq . ( set frequency in Hz )
|
||||
"saw" s 60 note . ( set MIDI note )
|
||||
"square" s 0.01 detune . ( slight detune )
|
||||
"lead" s 12 coarse . ( coarse tune in semitones )
|
||||
"bass" s 0.1 glide . ( portamento )
|
||||
```
|
||||
|
||||
## Pulse Width
|
||||
|
||||
For pulse/square oscillators:
|
||||
|
||||
```forth
|
||||
"pulse" s 0.3 pw . ( narrow pulse )
|
||||
"pulse" s 0.5 pw . ( square wave )
|
||||
```
|
||||
|
||||
## Stereo Spread
|
||||
|
||||
```forth
|
||||
"pad" s 0.5 spread . ( stereo spread amount )
|
||||
```
|
||||
|
||||
## Harmonics and Timbre
|
||||
|
||||
For oscillators that support it (like mutable):
|
||||
|
||||
```forth
|
||||
"mutable" s 4 harmonics . ( harmonic content )
|
||||
"mutable" s 0.5 timbre . ( timbre control )
|
||||
"mutable" s 0.3 morph . ( morph parameter )
|
||||
```
|
||||
|
||||
## Multiplier and Warp
|
||||
|
||||
```forth
|
||||
"fm" s 2 mult . ( frequency multiplier )
|
||||
"wt" s 0.5 warp . ( warp amount )
|
||||
"wt" s 1 mirror . ( mirror waveform )
|
||||
```
|
||||
|
||||
## Sub Oscillator
|
||||
|
||||
```forth
|
||||
"bass" s 0.5 sub . ( sub oscillator level )
|
||||
"bass" s 2 suboct . ( sub octave down )
|
||||
"bass" s 1 subwave . ( sub waveform )
|
||||
```
|
||||
|
||||
## Words
|
||||
|
||||
| Word | Stack | Description |
|
||||
|------|-------|-------------|
|
||||
| `freq` | (f --) | Set frequency (Hz) |
|
||||
| `note` | (n --) | Set MIDI note |
|
||||
| `detune` | (f --) | Set detune amount |
|
||||
| `coarse` | (f --) | Set coarse tune |
|
||||
| `glide` | (f --) | Set glide/portamento |
|
||||
| `pw` | (f --) | Set pulse width |
|
||||
| `spread` | (f --) | Set stereo spread |
|
||||
| `mult` | (f --) | Set multiplier |
|
||||
| `warp` | (f --) | Set warp amount |
|
||||
| `mirror` | (f --) | Set mirror |
|
||||
| `harmonics` | (f --) | Set harmonics |
|
||||
| `timbre` | (f --) | Set timbre |
|
||||
| `morph` | (f --) | Set morph |
|
||||
| `sub` | (f --) | Set sub oscillator level |
|
||||
| `suboct` | (n --) | Set sub octave |
|
||||
| `subwave` | (n --) | Set sub waveform |
|
||||
@@ -1,2 +0,0 @@
|
||||
# Parameters
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
# Pattern Management
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
# Pitch Envelope
|
||||
|
||||
Control pitch modulation over time with a dedicated ADSR envelope.
|
||||
|
||||
## Envelope Amount
|
||||
|
||||
```forth
|
||||
"bass" s 0.5 penv . ( pitch envelope depth )
|
||||
"bass" s -0.5 penv . ( negative envelope )
|
||||
```
|
||||
|
||||
## Envelope Shape
|
||||
|
||||
```forth
|
||||
"kick" s
|
||||
1.0 penv ( full envelope depth )
|
||||
0.001 patt ( instant attack )
|
||||
0.1 pdec ( fast decay )
|
||||
0 psus ( no sustain )
|
||||
0.1 prel ( short release )
|
||||
.
|
||||
```
|
||||
|
||||
## Classic Kick Drum
|
||||
|
||||
```forth
|
||||
"sine" s
|
||||
40 note
|
||||
2.0 penv
|
||||
0.001 patt
|
||||
0.05 pdec
|
||||
0 psus
|
||||
.
|
||||
```
|
||||
|
||||
## Words
|
||||
|
||||
| Word | Stack | Description |
|
||||
|------|-------|-------------|
|
||||
| `penv` | (f --) | Set pitch envelope amount |
|
||||
| `patt` | (f --) | Set pitch attack time |
|
||||
| `pdec` | (f --) | Set pitch decay time |
|
||||
| `psus` | (f --) | Set pitch sustain level |
|
||||
| `prel` | (f --) | Set pitch release time |
|
||||
@@ -1,2 +0,0 @@
|
||||
# Probability
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
# Randomness
|
||||
|
||||
@@ -1,76 +0,0 @@
|
||||
# Samples
|
||||
|
||||
Control sample playback with timing, looping, and slice parameters.
|
||||
|
||||
## Basic Playback
|
||||
|
||||
```forth
|
||||
"break" s . ( play sample from start )
|
||||
"break" s 0.5 speed . ( half speed )
|
||||
"break" s -1 speed . ( reverse )
|
||||
```
|
||||
|
||||
## Time and Position
|
||||
|
||||
Control where in the sample to start and end:
|
||||
|
||||
```forth
|
||||
"break" s 0.25 begin . ( start at 25% )
|
||||
"break" s 0.5 end . ( end at 50% )
|
||||
"break" s 0.1 time . ( offset start time )
|
||||
```
|
||||
|
||||
## Duration and Gate
|
||||
|
||||
```forth
|
||||
"pad" s 0.5 dur . ( play for 0.5 seconds )
|
||||
"pad" s 0.8 gate . ( gate time as fraction )
|
||||
```
|
||||
|
||||
## Looping
|
||||
|
||||
Fit a sample to a number of beats:
|
||||
|
||||
```forth
|
||||
"break" s 4 loop . ( fit to 4 beats )
|
||||
```
|
||||
|
||||
## Repetition
|
||||
|
||||
```forth
|
||||
"hat" s 4 repeat . ( trigger 4 times )
|
||||
```
|
||||
|
||||
## Voice and Routing
|
||||
|
||||
```forth
|
||||
"kick" s 1 voice . ( assign to voice 1 )
|
||||
"snare" s 0 orbit . ( route to bus 0 )
|
||||
```
|
||||
|
||||
## Sample Selection
|
||||
|
||||
```forth
|
||||
"kick" s 2 n . ( select sample #2 from folder )
|
||||
"kit" s "a" bank . ( use bank suffix )
|
||||
1 cut ( cut group - stops other sounds in same group )
|
||||
```
|
||||
|
||||
## Words
|
||||
|
||||
| Word | Stack | Description |
|
||||
|------|-------|-------------|
|
||||
| `time` | (f --) | Set time offset |
|
||||
| `repeat` | (n --) | Set repeat count |
|
||||
| `dur` | (f --) | Set duration |
|
||||
| `gate` | (f --) | Set gate time |
|
||||
| `speed` | (f --) | Set playback speed |
|
||||
| `begin` | (f --) | Set sample start (0-1) |
|
||||
| `end` | (f --) | Set sample end (0-1) |
|
||||
| `loop` | (n --) | Fit sample to n beats |
|
||||
| `voice` | (n --) | Set voice number |
|
||||
| `orbit` | (n --) | Set orbit/bus |
|
||||
| `n` | (n --) | Set sample number |
|
||||
| `bank` | (str --) | Set sample bank suffix |
|
||||
| `cut` | (n --) | Set cut group |
|
||||
| `reset` | (n --) | Reset parameter |
|
||||
@@ -1,2 +0,0 @@
|
||||
# Scales
|
||||
|
||||
@@ -1,62 +0,0 @@
|
||||
# Selection
|
||||
|
||||
Cycle through values over time for evolving patterns.
|
||||
|
||||
## Step Cycle
|
||||
|
||||
`cycle` cycles through values based on step runs:
|
||||
|
||||
```forth
|
||||
60 64 67 3 cycle note ( cycle through C, E, G )
|
||||
```
|
||||
|
||||
Each time the step runs, it picks the next value.
|
||||
|
||||
## Pattern Cycle
|
||||
|
||||
`pcycle` cycles based on pattern iteration:
|
||||
|
||||
```forth
|
||||
60 64 67 3 pcycle note ( change note each pattern loop )
|
||||
```
|
||||
|
||||
## Emit-Time Cycle
|
||||
|
||||
`tcycle` creates a cycle list resolved at emit time, useful with `.!`:
|
||||
|
||||
```forth
|
||||
60 64 67 3 tcycle note 3 .! ( emit C, E, G in sequence )
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
Rotating bass notes:
|
||||
|
||||
```forth
|
||||
"bass" s
|
||||
c3 e3 g3 b3 4 cycle note
|
||||
.
|
||||
```
|
||||
|
||||
Evolving pattern over loops:
|
||||
|
||||
```forth
|
||||
"lead" s
|
||||
0.5 1.0 0.75 0.25 4 pcycle gain
|
||||
.
|
||||
```
|
||||
|
||||
Arpeggiated chord:
|
||||
|
||||
```forth
|
||||
"pluck" s
|
||||
c4 e4 g4 c5 4 tcycle note 4 .!
|
||||
```
|
||||
|
||||
## Words
|
||||
|
||||
| Word | Stack | Description |
|
||||
|------|-------|-------------|
|
||||
| `cycle` | (v1..vn n -- val) | Cycle by step runs |
|
||||
| `pcycle` | (v1..vn n -- val) | Cycle by pattern iteration |
|
||||
| `tcycle` | (v1..vn n -- list) | Create cycle list for emit-time |
|
||||
@@ -1,72 +0,0 @@
|
||||
# Sequencer
|
||||
|
||||
## Structure
|
||||
|
||||
The sequencer is organized into:
|
||||
|
||||
- **Banks**: 16 banks (B01-B16)
|
||||
- **Patterns**: 16 patterns per bank (P01-P16)
|
||||
- **Steps**: Up to 32 steps per pattern
|
||||
- **Slots**: 8 concurrent playback slots
|
||||
|
||||
## Patterns
|
||||
|
||||
Each pattern has:
|
||||
|
||||
- **Length**: Number of steps (1-32)
|
||||
- **Speed**: Playback rate relative to tempo
|
||||
- **Steps**: Each step can have a script
|
||||
|
||||
### Speed Settings
|
||||
|
||||
- 1/4: Quarter speed
|
||||
- 1/2: Half speed
|
||||
- 1x: Normal speed
|
||||
- 2x: Double speed
|
||||
- 4x: Quadruple speed
|
||||
|
||||
## Slots
|
||||
|
||||
Slots allow multiple patterns to play simultaneously.
|
||||
|
||||
- Press **1-8** to toggle a slot
|
||||
- Slot changes are quantized to the next bar
|
||||
- A "?" indicates a slot queued to start
|
||||
- A "x" indicates a slot queued to stop
|
||||
|
||||
### Workflow
|
||||
|
||||
1. Edit a pattern in the main view
|
||||
2. Press **g** to queue it to the first free slot
|
||||
3. It starts playing at the next bar boundary
|
||||
4. Press **G** to queue its removal
|
||||
|
||||
## Steps
|
||||
|
||||
Steps are the basic unit of the sequencer:
|
||||
|
||||
- Navigate with arrow keys
|
||||
- Toggle active with Enter
|
||||
- Each step can contain a Rhai script
|
||||
|
||||
### Active vs Inactive
|
||||
|
||||
- Active steps (highlighted) execute their script
|
||||
- Inactive steps are skipped during playback
|
||||
- Toggle with Enter key
|
||||
|
||||
## Playback
|
||||
|
||||
The sequencer uses Ableton Link for timing:
|
||||
|
||||
- Syncs with other Link-enabled apps
|
||||
- Bar boundaries are used for slot changes
|
||||
- Phase shows position within the current bar
|
||||
|
||||
## Files
|
||||
|
||||
Projects are saved as JSON files:
|
||||
|
||||
- **s**: Save with dialog
|
||||
- **l**: Load with dialog
|
||||
- File extension: `.buboseq`
|
||||
@@ -1,2 +0,0 @@
|
||||
# Sound Basics
|
||||
|
||||
159
docs/stack.md
159
docs/stack.md
@@ -1,118 +1,95 @@
|
||||
# The Stack
|
||||
|
||||
Forth is a stack-based language. Instead of variables and expressions, you push values onto a stack and use words that consume and produce values.
|
||||
The stack is the heart of Forth. Every value you type goes onto the stack. Every word you call takes values from the stack and puts results back. There are no variables in the traditional sense, just this pile of values that grows and shrinks as your program runs.
|
||||
|
||||
## How It Works
|
||||
## Pushing Values
|
||||
|
||||
The stack is a last-in, first-out (LIFO) structure. Values you type get pushed on top. Words pop values off and push results back.
|
||||
When you type a number or a string, it goes on top of the stack:
|
||||
|
||||
```
|
||||
3 4 +
|
||||
```
|
||||
| Input | Stack (top on right) |
|
||||
|-------|---------------------|
|
||||
| `3` | `3` |
|
||||
| `4` | `3 4` |
|
||||
| `5` | `3 4 5` |
|
||||
|
||||
Step by step:
|
||||
1. `3` → push 3 onto stack: `[3]`
|
||||
2. `4` → push 4 onto stack: `[3, 4]`
|
||||
3. `+` → pop two values, add them, push result: `[7]`
|
||||
The stack grows to the right. The rightmost value is the top.
|
||||
|
||||
## Values
|
||||
## Words Consume and Produce
|
||||
|
||||
Three types can live on the stack:
|
||||
Words take values from the top and push results back. The `+` word pops two numbers and pushes their sum:
|
||||
|
||||
- **Integers**: `42`, `-7`, `0`
|
||||
- **Floats**: `3.14`, `0.5`, `-1.0`
|
||||
- **Strings**: `"kick"`, `"hello"`
|
||||
| Input | Stack |
|
||||
|-------|-------|
|
||||
| `3` | `3` |
|
||||
| `4` | `3 4` |
|
||||
| `+` | `7` |
|
||||
|
||||
This is why Forth uses postfix notation: operands come first, then the operator.
|
||||
|
||||
## Stack Notation
|
||||
|
||||
Documentation uses stack effect notation:
|
||||
Documentation describes what words do using stack effect notation:
|
||||
|
||||
```
|
||||
( before -- after )
|
||||
```
|
||||
|
||||
For example, `+` has effect `( a b -- sum )` meaning it takes two values and leaves one.
|
||||
The word `+` has the effect `( a b -- sum )`. It takes two values and leaves one.
|
||||
The word `dup` has the effect `( a -- a a )`. It takes one value and leaves two.
|
||||
|
||||
## Core Words
|
||||
## Thinking in Stack
|
||||
|
||||
### dup
|
||||
The key to Forth is learning to visualize the stack as you write. Consider this program:
|
||||
|
||||
Duplicate the top value.
|
||||
| Input | Stack | What happens |
|
||||
|-------|-------|--------------|
|
||||
| `3` | `3` | Push 3 |
|
||||
| `4` | `3 4` | Push 4 |
|
||||
| `+` | `7` | Add them |
|
||||
| `2` | `7 2` | Push 2 |
|
||||
| `*` | `14` | Multiply |
|
||||
|
||||
```
|
||||
3 dup ( 3 3 )
|
||||
This computes `(3 + 4) * 2`. The parentheses are implicit in the order of operations. You can use line breaks and white spaces to keep organized, and the editor will also show the stack at each step for you if you ask it nicely :)
|
||||
|
||||
## Rearranging Values
|
||||
|
||||
Sometimes you need values in a different order. Stack manipulation words like `dup`, `swap`, `drop`, and `over` let you shuffle things around. You will find them in the dictionary. Here is a common pattern. You want to use a value twice:
|
||||
|
||||
| Input | Stack |
|
||||
|-------|-------|
|
||||
| `3` | `3` |
|
||||
| `dup` | `3 3` |
|
||||
| `+` | `6` |
|
||||
|
||||
The word `dup` duplicates the top, so `3 dup +` doubles the number.
|
||||
|
||||
Another pattern. You have two values but need them swapped:
|
||||
|
||||
| Input | Stack |
|
||||
|-------|-------|
|
||||
| `3` | `3` |
|
||||
| `4` | `3 4` |
|
||||
| `swap` | `4 3` |
|
||||
| `-` | `1` |
|
||||
|
||||
Without `swap`, `3 4 -` would compute `3 - 4 = -1`. With `swap`, you get `4 - 3 = 1`.
|
||||
|
||||
## Stack Errors
|
||||
|
||||
Two things can go wrong with the stack:
|
||||
|
||||
* **Stack underflow** happens when a word needs more values than the stack has. If you write `+` with only one number on the stack, there is nothing to add. The script stops with an error.
|
||||
|
||||
```forth
|
||||
3 + ;; error: stack underflow
|
||||
```
|
||||
|
||||
### drop
|
||||
The fix is simple: make sure you push enough values before calling a word. Check the stack effect in the dictionary if you are unsure.
|
||||
|
||||
Discard the top value.
|
||||
* **Stack overflow** is the opposite: too many values left on the stack. This is less critical but indicates sloppy code. If your script leaves unused values behind, you probably made a mistake somewhere.
|
||||
|
||||
```
|
||||
3 4 drop ( 3 )
|
||||
```forth
|
||||
3 4 5 + . ;; plays a sound, but 3 is still on the stack
|
||||
```
|
||||
|
||||
### swap
|
||||
|
||||
Swap the top two values.
|
||||
|
||||
```
|
||||
3 4 swap ( 4 3 )
|
||||
```
|
||||
|
||||
### over
|
||||
|
||||
Copy the second value to the top.
|
||||
|
||||
```
|
||||
3 4 over ( 3 4 3 )
|
||||
```
|
||||
|
||||
### rot
|
||||
|
||||
Rotate the top three values.
|
||||
|
||||
```
|
||||
1 2 3 rot ( 2 3 1 )
|
||||
```
|
||||
|
||||
### nip
|
||||
|
||||
Drop the second value.
|
||||
|
||||
```
|
||||
3 4 nip ( 4 )
|
||||
```
|
||||
|
||||
### tuck
|
||||
|
||||
Copy top value below second.
|
||||
|
||||
```
|
||||
3 4 tuck ( 4 3 4 )
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
Build a chord by duplicating and adding:
|
||||
|
||||
```
|
||||
60 dup 4 + swap 7 + ( 64 67 60 )
|
||||
```
|
||||
|
||||
Use `over` to keep a base value:
|
||||
|
||||
```
|
||||
c4 over M3 swap P5 ( e4 g4 c4 )
|
||||
```
|
||||
|
||||
## Words
|
||||
|
||||
| Word | Stack | Description |
|
||||
|------|-------|-------------|
|
||||
| `dup` | (a -- a a) | Duplicate top |
|
||||
| `drop` | (a --) | Discard top |
|
||||
| `swap` | (a b -- b a) | Swap top two |
|
||||
| `over` | (a b -- a b a) | Copy second to top |
|
||||
| `rot` | (a b c -- b c a) | Rotate three |
|
||||
| `nip` | (a b -- b) | Drop second |
|
||||
| `tuck` | (a b -- b a b) | Copy top below second |
|
||||
The `3` was never used. Either it should not be there, or you forgot a word that consumes it.
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
# Tempo & Speed
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
# Timing
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
# Variables
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
# Vibrato
|
||||
|
||||
Add pitch vibrato to oscillator sounds.
|
||||
|
||||
## Basic Vibrato
|
||||
|
||||
```forth
|
||||
"lead" s
|
||||
60 note
|
||||
5 vib ( vibrato rate in Hz )
|
||||
0.5 vibmod ( vibrato depth )
|
||||
.
|
||||
```
|
||||
|
||||
## Vibrato Shape
|
||||
|
||||
Control the LFO waveform:
|
||||
|
||||
```forth
|
||||
"pad" s
|
||||
c4 note
|
||||
4 vib
|
||||
0.3 vibmod
|
||||
0 vibshape ( 0=sine, other values for different shapes )
|
||||
.
|
||||
```
|
||||
|
||||
## Subtle vs Expressive
|
||||
|
||||
```forth
|
||||
( subtle vibrato for pads )
|
||||
"pad" s 3 vib 0.1 vibmod .
|
||||
|
||||
( expressive vibrato for leads )
|
||||
"lead" s 6 vib 0.8 vibmod .
|
||||
```
|
||||
|
||||
## Words
|
||||
|
||||
| Word | Stack | Description |
|
||||
|------|-------|-------------|
|
||||
| `vib` | (f --) | Set vibrato rate (Hz) |
|
||||
| `vibmod` | (f --) | Set vibrato depth |
|
||||
| `vibshape` | (f --) | Set vibrato LFO shape |
|
||||
@@ -1,55 +0,0 @@
|
||||
# Wavetables
|
||||
|
||||
Control wavetable synthesis parameters for scanning through waveforms.
|
||||
|
||||
## Scan Position
|
||||
|
||||
The `scan` parameter controls which waveform in the wavetable is active:
|
||||
|
||||
```forth
|
||||
"wt" s 0.0 scan . ( first waveform )
|
||||
"wt" s 0.5 scan . ( middle of table )
|
||||
"wt" s 1.0 scan . ( last waveform )
|
||||
```
|
||||
|
||||
## Wavetable Length
|
||||
|
||||
Set the cycle length in samples:
|
||||
|
||||
```forth
|
||||
"wt" s 2048 wtlen . ( standard wavetable size )
|
||||
```
|
||||
|
||||
## Scan Modulation
|
||||
|
||||
Animate the scan position with an LFO:
|
||||
|
||||
```forth
|
||||
"wt" s 0.2 scanlfo . ( LFO rate in Hz )
|
||||
"wt" s 0.4 scandepth . ( LFO depth 0-1 )
|
||||
"wt" s "tri" scanshape . ( LFO shape )
|
||||
```
|
||||
|
||||
Available shapes: `sine`, `tri`, `saw`, `square`, `sh` (sample & hold)
|
||||
|
||||
## Example
|
||||
|
||||
```forth
|
||||
"wavetable" s
|
||||
60 note
|
||||
0.25 scan
|
||||
0.1 scanlfo
|
||||
0.3 scandepth
|
||||
"sine" scanshape
|
||||
.
|
||||
```
|
||||
|
||||
## Words
|
||||
|
||||
| Word | Stack | Description |
|
||||
|------|-------|-------------|
|
||||
| `scan` | (f --) | Set wavetable scan position (0-1) |
|
||||
| `wtlen` | (n --) | Set wavetable cycle length in samples |
|
||||
| `scanlfo` | (f --) | Set scan LFO rate (Hz) |
|
||||
| `scandepth` | (f --) | Set scan LFO depth (0-1) |
|
||||
| `scanshape` | (s --) | Set scan LFO shape |
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user