2 Commits

Author SHA1 Message Date
52cc890a67 Feat: website WIP and new words
Some checks failed
Deploy Website / deploy (push) Failing after 4m50s
2026-02-06 16:19:09 +01:00
0f9d750069 Feat: trying to improve bundling and compilation 2026-02-06 00:46:40 +01:00
15 changed files with 220 additions and 166 deletions

View File

@@ -5,6 +5,7 @@ All notable changes to this project will be documented in this file.
## [0.0.8] - 2026-06-05
### Added
- Proper desktop app icon and metadata across all platforms: moved icon to `assets/Cagire.png`, added Windows `.exe` icon and file properties embedding via `winres` build script, added PNG to cargo-bundle icon list for Linux `.deb` packaging.
- Universal macOS `.pkg` installer in CI: combines Intel and Apple Silicon builds into fat binaries via `lipo`, then packages `Cagire.app` and CLI into a single `.pkg` installer. Releases now include `cagire-macos-universal`, `cagire-macos-universal-desktop.app.zip`, and `Cagire-<version>-universal.pkg`.
- New themes: **Eden** (dark forest — black background with green-only palette, terminal aesthetic) and **Georges** (Commodore 64 palette on pure black background).
- `bounce` word: ping-pong cycle through n items by step runs (e.g., `60 64 67 72 4 bounce` → 60 64 67 72 67 64 60 64...).

View File

@@ -80,6 +80,9 @@ soft_ratatui = { version = "0.1.3", features = ["unicodefonts"], optional = true
image = { version = "0.25", default-features = false, features = ["png"], optional = true }
[target.'cfg(windows)'.build-dependencies]
winres = "0.1"
[profile.release]
opt-level = 3
lto = "fat"
@@ -90,7 +93,7 @@ strip = true
[package.metadata.bundle.bin.cagire-desktop]
name = "Cagire"
identifier = "com.sova.cagire"
icon = ["assets/Cagire.icns", "assets/Cagire.ico"]
icon = ["assets/Cagire.icns", "assets/Cagire.ico", "assets/Cagire.png"]
copyright = "Copyright (c) 2025 Raphaël Forment"
category = "Music"
short_description = "Forth-based music sequencer"

View File

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 26 KiB

11
build.rs Normal file
View File

@@ -0,0 +1,11 @@
fn main() {
#[cfg(windows)]
{
let mut res = winres::WindowsResource::new();
res.set_icon("assets/Cagire.ico")
.set("ProductName", "Cagire")
.set("FileDescription", "Forth-based music sequencer")
.set("LegalCopyright", "Copyright (c) 2025 Raphaël Forment");
res.compile().expect("Failed to compile Windows resources");
}
}

View File

@@ -789,4 +789,64 @@ pub(super) const WORDS: &[Word] = &[
compile: Param,
varargs: false,
},
Word {
name: "feedback",
aliases: &["fb"],
category: "Mod FX",
stack: "(f --)",
desc: "Set feedback delay level",
example: "0.7 feedback",
compile: Param,
varargs: false,
},
Word {
name: "fbtime",
aliases: &["fbt"],
category: "Mod FX",
stack: "(f --)",
desc: "Set feedback delay time in ms",
example: "30 fbtime",
compile: Param,
varargs: false,
},
Word {
name: "fbdamp",
aliases: &["fbd"],
category: "Mod FX",
stack: "(f --)",
desc: "Set feedback delay damping",
example: "0.3 fbdamp",
compile: Param,
varargs: false,
},
Word {
name: "fblfo",
aliases: &[],
category: "Mod FX",
stack: "(f --)",
desc: "Set feedback delay LFO rate in Hz",
example: "2 fblfo",
compile: Param,
varargs: false,
},
Word {
name: "fblfodepth",
aliases: &[],
category: "Mod FX",
stack: "(f --)",
desc: "Set feedback delay LFO depth",
example: "0.5 fblfodepth",
compile: Param,
varargs: false,
},
Word {
name: "fblfoshape",
aliases: &[],
category: "Mod FX",
stack: "(s --)",
desc: "Set feedback delay LFO shape",
example: "tri fblfoshape",
compile: Param,
varargs: false,
},
];

View File

@@ -458,7 +458,7 @@ impl eframe::App for CagireDesktop {
}
fn load_icon() -> egui::IconData {
const ICON_BYTES: &[u8] = include_bytes!("../../cagire_pixel.png");
const ICON_BYTES: &[u8] = include_bytes!("../../assets/Cagire.png");
let img = image::load_from_memory(ICON_BYTES)
.expect("Failed to load embedded icon")

BIN
website/public/Cagire.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
website/public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 137 KiB

Binary file not shown.

View File

@@ -14,65 +14,28 @@ toggle.addEventListener('click', () => {
const light = root.classList.contains('light');
toggle.textContent = light ? 'DARK' : 'LIGHT';
localStorage.setItem('theme', light ? 'light' : 'dark');
highlightForth();
});
function highlightForth() {
const words = ['note', 'sound', 'lpf', 'hpf', 'chorus', 'verb', 'distort', 'speed'];
const notes = ['c4'];
const chords = ['min7'];
const samples = ['kkick', 'sine', 'saw'];
const isLight = document.documentElement.classList.contains('light');
const numColor = isLight ? '#a855f7' : '#e8a0e8';
const dotColor = isLight ? '#0284c7' : '#7dd3fc';
const wordColor = isLight ? '#65a30d' : '#a3e635';
const noteColor = isLight ? '#d97706' : '#fbbf24';
const chordColor = isLight ? '#15803d' : '#4ade80';
const sampleColor = isLight ? '#dc2626' : '#f87171';
document.querySelectorAll('pre').forEach(pre => {
const text = pre.dataset.source || pre.textContent;
pre.dataset.source = text;
pre.innerHTML = text
.split(/(\s+)/)
.map(t => {
if (t === '.') return `<span style="color:${dotColor}">.</span>`;
if (/^-?\d+\.?\d*$/.test(t)) return `<span style="color:${numColor}">${t}</span>`;
if (words.includes(t)) return `<span style="color:${wordColor}">${t}</span>`;
if (notes.includes(t)) return `<span style="color:${noteColor}">${t}</span>`;
if (chords.includes(t)) return `<span style="color:${chordColor}">${t}</span>`;
if (samples.includes(t)) return `<span style="color:${sampleColor}">${t}</span>`;
return t;
})
.join('');
});
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', highlightForth);
} else {
highlightForth();
}
const kofiModal = document.getElementById('kofi-modal');
const kofiFrame = document.getElementById('kofi-frame');
document.querySelectorAll('.downloads-table a').forEach(link => {
link.addEventListener('click', () => {
if (sessionStorage.getItem('kofi-dismissed')) return;
kofiFrame.src = 'https://ko-fi.com/raphaelbubo/?hidefeed=true&widget=true&embed=true';
kofiModal.showModal();
document.querySelectorAll('.example-cell').forEach(cell => {
cell.addEventListener('click', () => {
const video = cell.querySelector('video');
const wasExpanded = cell.classList.contains('expanded');
document.querySelectorAll('.example-cell.expanded').forEach(c => {
c.classList.remove('expanded');
c.querySelector('video').pause();
});
if (!wasExpanded) {
cell.classList.add('expanded');
video.play();
}
});
});
kofiModal.addEventListener('close', () => {
sessionStorage.setItem('kofi-dismissed', '1');
kofiFrame.src = 'about:blank';
});
document.getElementById('kofi-close').addEventListener('click', () => {
kofiModal.close();
});
kofiModal.addEventListener('click', (e) => {
if (e.target === kofiModal) kofiModal.close();
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape') {
document.querySelectorAll('.example-cell.expanded').forEach(c => {
c.classList.remove('expanded');
c.querySelector('video').pause();
});
}
});

View File

@@ -1,6 +1,15 @@
@font-face {
font-family: 'CozetteVector';
src: url('/CozetteVector.ttf') format('truetype');
font-family: 'Space Mono';
src: url('/SpaceMono-Regular.woff2') format('woff2');
font-weight: 400;
font-display: swap;
}
@font-face {
font-family: 'Space Mono';
src: url('/SpaceMono-Bold.woff2') format('woff2');
font-weight: 700;
font-display: swap;
}
:root {
@@ -20,14 +29,28 @@
}
body {
font-family: 'CozetteVector', monospace;
font-family: 'Space Mono', monospace;
background: var(--bg);
color: var(--text);
max-width: 800px;
margin: 0 auto;
padding: 1rem;
line-height: 1.3;
font-size: 0.9rem;
font-size: clamp(0.9rem, 0.75rem + 0.75vw, 1.15rem);
}
header {
display: flex;
align-items: center;
gap: 0.75rem;
margin-bottom: 1.5rem;
}
.icon {
width: 4rem;
height: 4rem;
image-rendering: pixelated;
flex-shrink: 0;
}
h1 {
@@ -35,11 +58,18 @@ h1 {
margin: 0;
}
.subtitle {
color: var(--text-muted);
margin: 0;
}
h2 {
color: var(--text);
margin-top: 1rem;
margin-bottom: 0.25rem;
font-size: 1rem;
margin-top: 2rem;
margin-bottom: 1rem;
font-size: 1.15rem;
padding-bottom: 0.5rem;
border-bottom: 1px solid var(--text-muted);
}
p { margin: 0.25rem 0; }
@@ -59,11 +89,47 @@ ul {
li { margin: 0.1rem 0; }
pre {
.examples-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 0.5rem;
margin: 0.5rem 0 2rem;
}
.example-cell {
aspect-ratio: 1;
overflow: hidden;
background: var(--surface);
padding: 0.5rem;
overflow-x: auto;
margin: 0.25rem 0;
cursor: pointer;
}
.example-cell video {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
}
.example-cell.expanded {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
z-index: 100;
background: rgba(0, 0, 0, 0.9);
aspect-ratio: auto;
display: flex;
align-items: center;
justify-content: center;
}
.example-cell.expanded video {
width: auto;
height: auto;
max-width: 90vw;
max-height: 90vh;
object-fit: contain;
}
.downloads-table {
@@ -91,69 +157,34 @@ pre {
}
.note {
color: var(--text-muted);
color: var(--text-dim);
font-size: 0.8rem;
font-style: italic;
background: var(--surface);
padding: 0.5rem 0.75rem;
margin-top: 0.75rem;
}
.note::before {
content: '→ ';
}
.note a {
color: var(--text-muted);
}
.support {
background: var(--surface);
padding: 0.5rem;
margin: 0.5rem 0;
.colophon {
margin-top: 3rem;
padding-top: 1rem;
border-top: 1px solid var(--text-muted);
color: var(--text-muted);
}
.colophon a {
color: var(--text-dim);
}
.support a {
color: var(--text);
}
#kofi-modal {
background: var(--bg);
color: var(--text);
border: 1px solid var(--text-muted);
border-radius: 0;
padding: 1rem;
max-width: 420px;
width: 90vw;
}
#kofi-modal::backdrop {
background: rgba(0, 0, 0, 0.7);
}
#kofi-modal p {
margin-bottom: 0.5rem;
}
#kofi-frame {
border: none;
width: 100%;
height: 570px;
}
#kofi-close {
font-family: 'CozetteVector', monospace;
background: none;
color: var(--text-muted);
border: none;
padding: 0;
cursor: pointer;
font-size: inherit;
text-decoration: underline;
display: block;
margin: 0.5rem auto 0;
}
#kofi-close:hover {
color: var(--text);
}
#theme-toggle {
font-family: 'CozetteVector', monospace;
font-family: 'Space Mono', monospace;
background: none;
color: var(--text-muted);
border: none;

View File

@@ -18,19 +18,42 @@
<meta name="twitter:card" content="summary">
<meta name="twitter:title" content="Cagire - Forth-based live coding sequencer">
<meta name="twitter:description" content="Forth-based live coding music sequencer">
<link rel="icon" href="/favicon.ico">
<meta property="og:image" content="/Cagire.png">
<link rel="stylesheet" href="/style.css">
</head>
<body>
<h1>CAGIRE: LIVE CODING IN FORTH</h1>
<header>
<img class="icon" src="/Cagire.png" alt="Cagire">
<div>
<h1>CAGIRE: LIVE CODING IN FORTH</h1>
<p class="subtitle">AGPL-3.0 · Raphaël Maurice Forment · 2026</p>
</div>
</header>
<p class="support">Cagire is free and open source. If you find it useful, consider <a href="https://ko-fi.com/raphaelbubo">supporting the project on Ko-fi</a>.</p>
<div class="examples-grid">
<div class="example-cell"><video src="" muted loop playsinline></video></div>
<div class="example-cell"><video src="" muted loop playsinline></video></div>
<div class="example-cell"><video src="" muted loop playsinline></video></div>
<div class="example-cell"><video src="" muted loop playsinline></video></div>
<div class="example-cell"><video src="" muted loop playsinline></video></div>
<div class="example-cell"><video src="" muted loop playsinline></video></div>
<div class="example-cell"><video src="" muted loop playsinline></video></div>
<div class="example-cell"><video src="" muted loop playsinline></video></div>
<div class="example-cell"><video src="" muted loop playsinline></video></div>
</div>
<h2>Download</h2>
<table class="downloads-table">
<tr>
<th>Platform</th>
<th>Desktop</th>
<th>Terminal</th>
</tr>
<tr>
<td>macOS (Universal)</td>
<td colspan="2"><a href="https://github.com/Bubobubobubobubo/cagire/releases/latest/download/cagire-macos-universal.pkg">.pkg</a></td>
</tr>
<tr>
<td>macOS (ARM)</td>
<td><a href="https://github.com/Bubobubobubobubo/cagire/releases/latest/download/cagire-macos-aarch64-desktop.app.zip">.app</a></td>
@@ -54,65 +77,27 @@
</table>
<p class="note">All releases are available on <a href="https://github.com/Bubobubobubobubo/cagire/releases/latest">GitHub</a>. You can also compile the software yourself by getting it from Cargo!</p>
<dialog id="kofi-modal">
<p>If you enjoy Cagire, consider supporting the project:</p>
<iframe id="kofi-frame" src="about:blank" title="Ko-fi donation widget"></iframe>
<button id="kofi-close">CLOSE</button>
</dialog>
<video src="/mono_cagire.mp4" autoplay muted loop playsinline></video>
<h2>About</h2>
<p>Cagire is a step sequencer where each step contains a Forth script instead of typical note data. When the sequencer reaches a step, it runs the associated script. Scripts can produce sound, trigger samples, apply effects, or do nothing at all. You are free to define what your scripts will do. Cagire includes a built-in audio engine called <a href="https://doux.livecoding.fr">Doux</a>. No external software is needed to make sound. It comes with oscillators, sample players, filters, reverb, delay, distortion, and more.</p>
<h2>Code Examples</h2>
<br>
<p>A minimal script that plays a middle C note using a sine wave:</p>
<pre>c4 note sine sound .</pre>
<br>
<p>And now let's make it polyphonic and add different parameters per voice:</p>
<pre>c4 min7 note
sine sound
0.1 chorus
500 1000 1500 lpf
.</pre>
<br>
<p>Sawtooth wave with lowpass filter, chorus and reverb:</p>
<pre>saw sound 1200 lpf 0.2 chorus 0.8 verb .</pre>
<br>
<p>Pitched-down kick drum sample with distortion:</p>
<pre>kkick sound 1.5 distort 0.8 speed .</pre>
<br>
<h2>Features</h2>
<ul>
<li>Robust synthesis engine: synthesizers, sampling, effects, live input, and more to discover.</li>
<li>Ableton Link: jam with your friends or include other software / hardware to your setup.</li>
<li>32 banks × 32 patterns × 128 steps per project: (~131.000 scripts per project).</li>
<li>Forth: objectively the coolest / minimal / hackable language to make music with!</li>
<li>Embedded dictionary and documentation!</li>
<li>Embedded dictionary and documentation! Learn while coding!</li>
</ul>
<h2>Live Coding</h2>
<p>Live coding is a technique where a programmer writes code in real-time, often in front of an audience. It can be used to create music, visual art, and other forms of media. Learn more at <a href="https://toplap.org">TOPLAP</a> or <a href="https://livecoding.fr">livecoding.fr</a>.</p>
<p>Live coding is a technique where a programmer writes code in real-time in front of an audience. It is a way to experiment with code, to share things and thoughts openly, to express yourself through code. It can be technical, poetical, weird, preferably all at once. Live coding can be used to create music, visual art, and other forms of media. Live coding is an autotelic activity: doing it is its own reward. There are no errors, only fun. Learn more at <a href="https://toplap.org">TOPLAP</a> or <a href="https://livecoding.fr">livecoding.fr</a>.</p>
<h2>Credits</h2>
<ul>
<li><a href="https://raphaelforment.fr">BuboBubo</a> (Raphaël Maurice Forment).</li>
<li>See <a href="https://doux.livecoding.fr">Doux</a> for engine credits.</li>
</ul>
<h2>Links</h2>
<ul>
<li><a href="https://github.com/Bubobubobubobubo/cagire">GitHub</a></li>
<li><a href="https://ko-fi.com/raphaelbubo">Ko-fi</a></li>
</ul>
<p style="margin-top: 2rem; color: var(--text-muted);">AGPL-3.0 License · <button id="theme-toggle" aria-label="Toggle theme">LIGHT</button></p>
<p class="colophon">
<a href="https://raphaelforment.fr">BuboBubo</a> · Audio engine: <a href="https://doux.livecoding.fr">Doux</a> · <a href="https://github.com/Bubobubobubobubo/cagire">GitHub</a> · AGPL-3.0 · <button id="theme-toggle" aria-label="Toggle theme">LIGHT</button>
</p>
<script is:inline src="/script.js"></script>
</body>