feat: ceiling-hit markers as top-edge ticks on the rolling plot

Flag each plot bucket with whether the output limiter hit the ceiling
(limiter GR > 0.1 dB), carried through ScopeRing as a per-bucket hit field and
folded into history columns. The editor draws a short red tick at the top of any
hit column (one column wide, so runs merge into segments and a lone hit is a
dot); nothing otherwise. Global marker, shown on every channel tab.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Mikkeli Matlock
2026-06-24 22:16:33 +09:00
parent 42c5f7dcd2
commit a8be2179c3
3 changed files with 60 additions and 11 deletions
+11 -1
View File
@@ -46,11 +46,16 @@ struct Codename206 {
scope_in: [f32; 4],
scope_out: [f32; 4],
scope_gr: [f32; 4],
/// Max output-limiter gain reduction seen in the current bucket (for the ceiling-hit marker).
scope_hit: f32,
/// Samples accumulated into the current bucket, and the bucket length (= sample_rate / BUCKET_HZ).
scope_samples: usize,
scope_bucket_len: usize,
}
/// Limiter gain reduction (dB) above which a plot bucket is flagged as hitting the ceiling.
const CEILING_HIT_GR_DB: f32 = 0.1;
impl Default for Codename206 {
fn default() -> Self {
Self {
@@ -64,6 +69,7 @@ impl Default for Codename206 {
scope_in: [0.0; 4],
scope_out: [0.0; 4],
scope_gr: [0.0; 4],
scope_hit: 0.0,
scope_samples: 0,
scope_bucket_len: 1,
}
@@ -164,6 +170,7 @@ impl Plugin for Codename206 {
self.scope_in = [0.0; 4];
self.scope_out = [0.0; 4];
self.scope_gr = [0.0; 4];
self.scope_hit = 0.0;
self.scope_samples = 0;
}
@@ -287,14 +294,17 @@ impl Plugin for Codename206 {
self.scope_in[ALL] = self.scope_in[ALL].max(in_mono);
self.scope_out[ALL] = self.scope_out[ALL].max(out_l.max(out_r));
self.scope_gr[ALL] = self.scope_gr[ALL].max(g);
self.scope_hit = self.scope_hit.max(self.limiter.gain_reduction_db());
// Emit a plot bucket every scope_bucket_len samples (~BUCKET_HZ).
self.scope_samples += 1;
if self.scope_samples >= self.scope_bucket_len {
self.meters.scope.push(&self.scope_in, &self.scope_out, &self.scope_gr);
let hit = if self.scope_hit > CEILING_HIT_GR_DB { 1.0 } else { 0.0 };
self.meters.scope.push(&self.scope_in, &self.scope_out, &self.scope_gr, hit);
self.scope_in = [0.0; meters::NUM_CHANNELS];
self.scope_out = [0.0; meters::NUM_CHANNELS];
self.scope_gr = [0.0; meters::NUM_CHANNELS];
self.scope_hit = 0.0;
self.scope_samples = 0;
}
}