Phase 3 (L8). Pure statement-for-statement relocation into dsp/ratio_seq.hpp, dsp/seq_clock.hpp and core/event_queue.hpp. AUDIT CORRECTION: L8 says "breakor and elysiamorf duplicate ratio_seq". That is false — ElysiamorfEngine has no ratio_seq at all; it triggers continuously via FM operators. The real duplicate pair is BreakOrEngine and MEMLCeliumEngine, whose copies are byte-for-byte identical. Elysiamorf did share the clock and event-queue machinery, so it uses those. memlcelium now includes the shared ratio_seq too, which is what actually closes this finding. Deliberately NOT folded into core/ring_buffer.hpp: RingBuffer is an atomics-based cross-core SPSC channel (its header says so), whereas the engines' event queue is produced and drained on one thread. Reusing it would have meant paying for atomics to serve a single-threaded FIFO. The distinction is now recorded in MAP.md so the next audit does not read them as duplicates. Bit-exactness: verified the MIDI-clock tick and bar-phasor tick preserve the original operation order with no floating-point re-association, and that EventQueue keeps the original `% N` indexing rather than adopting RingBuffer's bitmask. The golden suite (nisps_golden_tests) and the native<->WASM parity blob both pass unchanged — they are the check, and they were not re-baselined.
45 lines
1.8 KiB
C++
45 lines
1.8 KiB
C++
// nisps/dsp/ratio_seq.hpp — ratio/Euclidean-style pulse-width sequencer gate.
|
|
//
|
|
// Given a bar-relative phasor and a small set of integer-ish ratios summing
|
|
// to `ratio_sum`, splits the bar into N unequal beats (proportional to each
|
|
// ratio) and returns whether the phasor currently sits within the first
|
|
// `pulse_width` fraction of its beat. Used by every ratio-sequencer engine
|
|
// to decide trigger (3 ratios) and accent/high-amp (2 ratios) gates.
|
|
//
|
|
// Extracted from the byte-for-byte-identical `ratio_seq<N>` template
|
|
// previously duplicated in nisps/engines/breakor.hpp and
|
|
// nisps/engines/memlcelium.hpp (2026-07 simplification audit, finding L8).
|
|
// NOTE: nisps/engines/elysiamorf.hpp does NOT use ratio_seq — it drives its
|
|
// tracks continuously via FM-pair oscillators (FMOp), not a ratio gate. The
|
|
// audit's finding text named breakor+elysiamorf as the ratio_seq duplicate;
|
|
// the actual duplicate pair is breakor+memlcelium (see MEMLCeliumEngine's
|
|
// private ratio_seq/ratio_seq_3/ratio_seq_2, out of this change's file
|
|
// ownership — a follow-up should point memlcelium.hpp at this header too).
|
|
|
|
#pragma once
|
|
|
|
#include <array>
|
|
#include <cstddef>
|
|
|
|
namespace nisps {
|
|
|
|
template <std::size_t N>
|
|
inline bool ratio_seq(float phasor, float ratio_sum,
|
|
const std::array<float, N>& ratios,
|
|
float pulse_width) noexcept {
|
|
float offset_phase = phasor;
|
|
if (offset_phase >= 1.f) offset_phase -= 1.f;
|
|
const float phase_adj = ratio_sum * offset_phase;
|
|
float accum = 0.f, last = 0.f;
|
|
for (std::size_t i = 0u; i < N; ++i) {
|
|
accum += ratios[i];
|
|
if (phase_adj <= accum) {
|
|
const float beat_phase = (phase_adj - last) / (accum - last);
|
|
return beat_phase <= pulse_width;
|
|
}
|
|
last = accum;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
} // namespace nisps
|