Mixed bag of things
This commit is contained in:
@@ -98,6 +98,7 @@ impl App {
|
|||||||
show_scope: self.audio.config.show_scope,
|
show_scope: self.audio.config.show_scope,
|
||||||
show_spectrum: self.audio.config.show_spectrum,
|
show_spectrum: self.audio.config.show_spectrum,
|
||||||
show_completion: self.ui.show_completion,
|
show_completion: self.ui.show_completion,
|
||||||
|
flash_brightness: self.ui.flash_brightness,
|
||||||
},
|
},
|
||||||
link: crate::settings::LinkSettings {
|
link: crate::settings::LinkSettings {
|
||||||
enabled: link.is_enabled(),
|
enabled: link.is_enabled(),
|
||||||
|
|||||||
@@ -1163,6 +1163,11 @@ fn handle_options_page(ctx: &mut InputContext, key: KeyEvent) -> InputResult {
|
|||||||
OptionsFocus::ShowCompletion => {
|
OptionsFocus::ShowCompletion => {
|
||||||
ctx.app.ui.show_completion = !ctx.app.ui.show_completion
|
ctx.app.ui.show_completion = !ctx.app.ui.show_completion
|
||||||
}
|
}
|
||||||
|
OptionsFocus::FlashBrightness => {
|
||||||
|
let delta = if key.code == KeyCode::Left { -0.1 } else { 0.1 };
|
||||||
|
ctx.app.ui.flash_brightness =
|
||||||
|
(ctx.app.ui.flash_brightness + delta).clamp(0.0, 1.0);
|
||||||
|
}
|
||||||
OptionsFocus::LinkEnabled => ctx.link.set_enabled(!ctx.link.is_enabled()),
|
OptionsFocus::LinkEnabled => ctx.link.set_enabled(!ctx.link.is_enabled()),
|
||||||
OptionsFocus::StartStopSync => ctx
|
OptionsFocus::StartStopSync => ctx
|
||||||
.link
|
.link
|
||||||
|
|||||||
@@ -94,6 +94,7 @@ fn main() -> io::Result<()> {
|
|||||||
app.audio.config.show_scope = settings.display.show_scope;
|
app.audio.config.show_scope = settings.display.show_scope;
|
||||||
app.audio.config.show_spectrum = settings.display.show_spectrum;
|
app.audio.config.show_spectrum = settings.display.show_spectrum;
|
||||||
app.ui.show_completion = settings.display.show_completion;
|
app.ui.show_completion = settings.display.show_completion;
|
||||||
|
app.ui.flash_brightness = settings.display.flash_brightness;
|
||||||
|
|
||||||
let metrics = Arc::new(EngineMetrics::default());
|
let metrics = Arc::new(EngineMetrics::default());
|
||||||
let scope_buffer = Arc::new(ScopeBuffer::new());
|
let scope_buffer = Arc::new(ScopeBuffer::new());
|
||||||
@@ -212,6 +213,13 @@ fn main() -> io::Result<()> {
|
|||||||
app.metrics.event_count = seq_snapshot.event_count;
|
app.metrics.event_count = seq_snapshot.event_count;
|
||||||
app.metrics.dropped_events = seq_snapshot.dropped_events;
|
app.metrics.dropped_events = seq_snapshot.dropped_events;
|
||||||
|
|
||||||
|
app.ui.event_flash = (app.ui.event_flash - 0.1).max(0.0);
|
||||||
|
let new_events = app.metrics.event_count.saturating_sub(app.ui.last_event_count);
|
||||||
|
if new_events > 0 {
|
||||||
|
app.ui.event_flash = (new_events as f32 * 0.4).min(1.0);
|
||||||
|
}
|
||||||
|
app.ui.last_event_count = app.metrics.event_count;
|
||||||
|
|
||||||
app.flush_queued_changes(&sequencer.cmd_tx);
|
app.flush_queued_changes(&sequencer.cmd_tx);
|
||||||
app.flush_dirty_patterns(&sequencer.cmd_tx);
|
app.flush_dirty_patterns(&sequencer.cmd_tx);
|
||||||
|
|
||||||
|
|||||||
@@ -29,8 +29,12 @@ pub struct DisplaySettings {
|
|||||||
pub show_spectrum: bool,
|
pub show_spectrum: bool,
|
||||||
#[serde(default = "default_true")]
|
#[serde(default = "default_true")]
|
||||||
pub show_completion: bool,
|
pub show_completion: bool,
|
||||||
|
#[serde(default = "default_flash_brightness")]
|
||||||
|
pub flash_brightness: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn default_flash_brightness() -> f32 { 1.0 }
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
pub struct LinkSettings {
|
pub struct LinkSettings {
|
||||||
pub enabled: bool,
|
pub enabled: bool,
|
||||||
@@ -60,6 +64,7 @@ impl Default for DisplaySettings {
|
|||||||
show_scope: true,
|
show_scope: true,
|
||||||
show_spectrum: true,
|
show_spectrum: true,
|
||||||
show_completion: true,
|
show_completion: true,
|
||||||
|
flash_brightness: 1.0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ pub enum OptionsFocus {
|
|||||||
ShowScope,
|
ShowScope,
|
||||||
ShowSpectrum,
|
ShowSpectrum,
|
||||||
ShowCompletion,
|
ShowCompletion,
|
||||||
|
FlashBrightness,
|
||||||
LinkEnabled,
|
LinkEnabled,
|
||||||
StartStopSync,
|
StartStopSync,
|
||||||
Quantum,
|
Quantum,
|
||||||
@@ -23,7 +24,8 @@ impl OptionsState {
|
|||||||
OptionsFocus::RuntimeHighlight => OptionsFocus::ShowScope,
|
OptionsFocus::RuntimeHighlight => OptionsFocus::ShowScope,
|
||||||
OptionsFocus::ShowScope => OptionsFocus::ShowSpectrum,
|
OptionsFocus::ShowScope => OptionsFocus::ShowSpectrum,
|
||||||
OptionsFocus::ShowSpectrum => OptionsFocus::ShowCompletion,
|
OptionsFocus::ShowSpectrum => OptionsFocus::ShowCompletion,
|
||||||
OptionsFocus::ShowCompletion => OptionsFocus::LinkEnabled,
|
OptionsFocus::ShowCompletion => OptionsFocus::FlashBrightness,
|
||||||
|
OptionsFocus::FlashBrightness => OptionsFocus::LinkEnabled,
|
||||||
OptionsFocus::LinkEnabled => OptionsFocus::StartStopSync,
|
OptionsFocus::LinkEnabled => OptionsFocus::StartStopSync,
|
||||||
OptionsFocus::StartStopSync => OptionsFocus::Quantum,
|
OptionsFocus::StartStopSync => OptionsFocus::Quantum,
|
||||||
OptionsFocus::Quantum => OptionsFocus::RefreshRate,
|
OptionsFocus::Quantum => OptionsFocus::RefreshRate,
|
||||||
@@ -37,7 +39,8 @@ impl OptionsState {
|
|||||||
OptionsFocus::ShowScope => OptionsFocus::RuntimeHighlight,
|
OptionsFocus::ShowScope => OptionsFocus::RuntimeHighlight,
|
||||||
OptionsFocus::ShowSpectrum => OptionsFocus::ShowScope,
|
OptionsFocus::ShowSpectrum => OptionsFocus::ShowScope,
|
||||||
OptionsFocus::ShowCompletion => OptionsFocus::ShowSpectrum,
|
OptionsFocus::ShowCompletion => OptionsFocus::ShowSpectrum,
|
||||||
OptionsFocus::LinkEnabled => OptionsFocus::ShowCompletion,
|
OptionsFocus::FlashBrightness => OptionsFocus::ShowCompletion,
|
||||||
|
OptionsFocus::LinkEnabled => OptionsFocus::FlashBrightness,
|
||||||
OptionsFocus::StartStopSync => OptionsFocus::LinkEnabled,
|
OptionsFocus::StartStopSync => OptionsFocus::LinkEnabled,
|
||||||
OptionsFocus::Quantum => OptionsFocus::StartStopSync,
|
OptionsFocus::Quantum => OptionsFocus::StartStopSync,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -38,6 +38,9 @@ pub struct UiState {
|
|||||||
pub runtime_highlight: bool,
|
pub runtime_highlight: bool,
|
||||||
pub show_completion: bool,
|
pub show_completion: bool,
|
||||||
pub minimap_until: Option<Instant>,
|
pub minimap_until: Option<Instant>,
|
||||||
|
pub last_event_count: usize,
|
||||||
|
pub event_flash: f32,
|
||||||
|
pub flash_brightness: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for UiState {
|
impl Default for UiState {
|
||||||
@@ -61,6 +64,9 @@ impl Default for UiState {
|
|||||||
runtime_highlight: false,
|
runtime_highlight: false,
|
||||||
show_completion: true,
|
show_completion: true,
|
||||||
minimap_until: None,
|
minimap_until: None,
|
||||||
|
last_event_count: 0,
|
||||||
|
event_flash: 0.0,
|
||||||
|
flash_brightness: 1.0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,7 +41,24 @@ pub fn render(frame: &mut Frame, app: &App, snapshot: &SequencerSnapshot, area:
|
|||||||
}
|
}
|
||||||
|
|
||||||
render_sequencer(frame, app, snapshot, sequencer_area);
|
render_sequencer(frame, app, snapshot, sequencer_area);
|
||||||
render_vu_meter(frame, app, vu_area);
|
|
||||||
|
// Calculate actual grid height to align VU meter
|
||||||
|
let pattern = app.current_edit_pattern();
|
||||||
|
let page = app.editor_ctx.step / STEPS_PER_PAGE;
|
||||||
|
let page_start = page * STEPS_PER_PAGE;
|
||||||
|
let steps_on_page = (page_start + STEPS_PER_PAGE).min(pattern.length) - page_start;
|
||||||
|
let num_rows = steps_on_page.div_ceil(8);
|
||||||
|
let spacing = num_rows.saturating_sub(1) as u16;
|
||||||
|
let row_height = sequencer_area.height.saturating_sub(spacing) / num_rows as u16;
|
||||||
|
let actual_grid_height = row_height * num_rows as u16 + spacing;
|
||||||
|
|
||||||
|
let aligned_vu_area = Rect {
|
||||||
|
y: sequencer_area.y,
|
||||||
|
height: actual_grid_height,
|
||||||
|
..vu_area
|
||||||
|
};
|
||||||
|
|
||||||
|
render_vu_meter(frame, app, aligned_vu_area);
|
||||||
}
|
}
|
||||||
|
|
||||||
const STEPS_PER_PAGE: usize = 32;
|
const STEPS_PER_PAGE: usize = 32;
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ pub fn render(frame: &mut Frame, app: &App, link: &LinkState, area: Rect) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let [display_area, _, link_area, _, session_area] = Layout::vertical([
|
let [display_area, _, link_area, _, session_area] = Layout::vertical([
|
||||||
Constraint::Length(7),
|
Constraint::Length(8),
|
||||||
Constraint::Length(1),
|
Constraint::Length(1),
|
||||||
Constraint::Length(5),
|
Constraint::Length(5),
|
||||||
Constraint::Length(1),
|
Constraint::Length(1),
|
||||||
@@ -63,6 +63,7 @@ fn render_display_section(frame: &mut Frame, app: &App, area: Rect) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
let focus = app.options.focus;
|
let focus = app.options.focus;
|
||||||
|
let flash_str = format!("{:.0}%", app.ui.flash_brightness * 100.0);
|
||||||
let lines = vec![
|
let lines = vec![
|
||||||
render_option_line(
|
render_option_line(
|
||||||
"Refresh rate",
|
"Refresh rate",
|
||||||
@@ -93,6 +94,11 @@ fn render_display_section(frame: &mut Frame, app: &App, area: Rect) {
|
|||||||
if app.ui.show_completion { "On" } else { "Off" },
|
if app.ui.show_completion { "On" } else { "Off" },
|
||||||
focus == OptionsFocus::ShowCompletion,
|
focus == OptionsFocus::ShowCompletion,
|
||||||
),
|
),
|
||||||
|
render_option_line(
|
||||||
|
"Flash brightness",
|
||||||
|
&flash_str,
|
||||||
|
focus == OptionsFocus::FlashBrightness,
|
||||||
|
),
|
||||||
];
|
];
|
||||||
|
|
||||||
frame.render_widget(Paragraph::new(lines), content_area);
|
frame.render_widget(Paragraph::new(lines), content_area);
|
||||||
|
|||||||
@@ -41,9 +41,20 @@ fn adjust_spans_for_line(
|
|||||||
|
|
||||||
pub fn render(frame: &mut Frame, app: &App, link: &LinkState, snapshot: &SequencerSnapshot) {
|
pub fn render(frame: &mut Frame, app: &App, link: &LinkState, snapshot: &SequencerSnapshot) {
|
||||||
let term = frame.area();
|
let term = frame.area();
|
||||||
|
|
||||||
|
let bg_color = if app.ui.event_flash > 0.0 {
|
||||||
|
let i = (app.ui.event_flash * app.ui.flash_brightness * 60.0) as u8;
|
||||||
|
Color::Rgb(i, i, i)
|
||||||
|
} else {
|
||||||
|
Color::Reset
|
||||||
|
};
|
||||||
|
|
||||||
let blank = " ".repeat(term.width as usize);
|
let blank = " ".repeat(term.width as usize);
|
||||||
let lines: Vec<Line> = (0..term.height).map(|_| Line::raw(&blank)).collect();
|
let lines: Vec<Line> = (0..term.height).map(|_| Line::raw(&blank)).collect();
|
||||||
frame.render_widget(Paragraph::new(lines), term);
|
frame.render_widget(
|
||||||
|
Paragraph::new(lines).style(Style::default().bg(bg_color)),
|
||||||
|
term,
|
||||||
|
);
|
||||||
|
|
||||||
if app.ui.show_title {
|
if app.ui.show_title {
|
||||||
title_view::render(frame, term, &app.ui);
|
title_view::render(frame, term, &app.ui);
|
||||||
|
|||||||
BIN
website/cagire.png
Normal file
BIN
website/cagire.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 152 KiB |
162
website/index.html
Normal file
162
website/index.html
Normal file
@@ -0,0 +1,162 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Cagire</title>
|
||||||
|
<style>
|
||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: 'SF Mono', 'Monaco', 'Inconsolata', 'Fira Code', monospace;
|
||||||
|
background: #0a0a0a;
|
||||||
|
color: #e0e0e0;
|
||||||
|
min-height: 100vh;
|
||||||
|
padding: 2rem;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
max-width: 70ch;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
border-bottom: 1px solid #333;
|
||||||
|
padding-bottom: 1rem;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ascii {
|
||||||
|
color: #64a0b4;
|
||||||
|
font-size: 0.7rem;
|
||||||
|
line-height: 1.1;
|
||||||
|
white-space: pre;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav {
|
||||||
|
margin-top: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav a {
|
||||||
|
color: #cccc44;
|
||||||
|
text-decoration: none;
|
||||||
|
margin-right: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav a:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
section {
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
color: #888;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
font-weight: bold;
|
||||||
|
margin-bottom: 0.75rem;
|
||||||
|
border-bottom: 1px solid #333;
|
||||||
|
padding-bottom: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.buttons {
|
||||||
|
display: flex;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
flex: 1;
|
||||||
|
padding: 0.75rem 1rem;
|
||||||
|
border: 1px solid #333;
|
||||||
|
color: #e0e0e0;
|
||||||
|
text-decoration: none;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn:hover {
|
||||||
|
border-color: #64a0b4;
|
||||||
|
color: #64a0b4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.screenshot img {
|
||||||
|
width: 100%;
|
||||||
|
border: 1px solid #333;
|
||||||
|
margin-bottom: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.screenshot p {
|
||||||
|
color: #888;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
color: #888;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
section a {
|
||||||
|
color: #cccc44;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
section a:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<div class="header">
|
||||||
|
<pre class="ascii">
|
||||||
|
██████╗ █████╗ ██████╗ ██╗██████╗ ███████╗
|
||||||
|
██╔════╝██╔══██╗██╔════╝ ██║██╔══██╗██╔════╝
|
||||||
|
██║ ███████║██║ ███╗██║██████╔╝█████╗
|
||||||
|
██║ ██╔══██║██║ ██║██║██╔══██╗██╔══╝
|
||||||
|
╚██████╗██║ ██║╚██████╔╝██║██║ ██║███████╗
|
||||||
|
╚═════╝╚═╝ ╚═╝ ╚═════╝ ╚═╝╚═╝ ╚═╝╚══════╝
|
||||||
|
</pre>
|
||||||
|
<nav>
|
||||||
|
<a href="#description">Description</a>
|
||||||
|
<a href="#releases">Releases</a>
|
||||||
|
<a href="#credits">Credits</a>
|
||||||
|
<a href="#support">Support</a>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<section id="description" class="screenshot">
|
||||||
|
<img src="cagire.png" alt="Cagire screenshot">
|
||||||
|
<p>Cagire is a terminal-based step sequencer for live coding music. Each step in a pattern contains a Forth script that produces sound and creates events. Synchronize with other musicians using Ableton Link. Cagire uses its own audio engine for audio synthesis and sampling!</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section id="releases">
|
||||||
|
<h2>Releases</h2>
|
||||||
|
<div class="buttons">
|
||||||
|
<a href="#" class="btn">macOS</a>
|
||||||
|
<a href="#" class="btn">Windows</a>
|
||||||
|
<a href="#" class="btn">Linux</a>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section id="credits">
|
||||||
|
<h2>Credits</h2>
|
||||||
|
<p>Cagire is built by BuboBubo (Raphael Maurice Forment).</p>
|
||||||
|
<p>Doux (audio engine) is a Rust port of Dough, originally written in C by Felix Roos.</p>
|
||||||
|
<p>mi-plaits-dsp-rs by Oliver Rockstedt, based on Mutable Instruments Plaits by Emilie Gillet.</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section id="support">
|
||||||
|
<h2>Support</h2>
|
||||||
|
<p>Report issues and contribute on <a href="https://github.com/bubo/cagire">GitHub</a>.</p>
|
||||||
|
<p>Support the project on <a href="https://ko-fi.com/raphaelbubo">Ko-fi</a>.</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Reference in New Issue
Block a user