Concept-based, no virtual dispatch, per-engine voice spaces as inline
methods. Each engine satisfies nisps::AudioEngine via static_assert.
- NoOpEngine: silent passthrough; used for sequencer-only modes and
for the SoundAnalysisMIDI mode's audio path.
- PAFSynthEngine (33 params, 7 voice spaces): 4-voice PAF synth with
detune cascade, ring-mod, sine-shaper, ADSR, feedback delay. note_on/
note_off interface for MIDI keyboard.
- ChannelStripEngine (24 params, 6 voice spaces): stereo console strip
(pre-gain/HPF/LPF/2x peak/low-shelf/high-shelf/comp/post-gain). Voice
spaces: WannabeNeve66, SSL4K, SSL9K, MaleVox, FemaleVox, Neve80
(stepped-frequency).
- XIASRIEngine (24 params, "Direct" voice space): pitch-shift + 6 allpass
+ 2 comb + 4 delays. Direct NN→param mapping per firmware semantics.
- VerbFXEngine (47 params, 12 voice spaces): 8-band SVF filterbank +
3-lane dynamic delay + 8-lpcomb/4-allpass Freeverb-style tail with
cross-fades. All 12 voice spaces ported from voicespaces/VerbFX/*.hpp.
- MEMLCeliumEngine (56 params): 2-track ratio sequencer + dual-voice
PAF synth (7+7+22+20 layout). Sequencer triggers V0/V1 ADSR.
- BreakOrEngine (56 params): 8-track ratio sequencer; emits NoteOn/
NoteOff/Clock events via pop_events(span). process() returns silence.
- ElysiamorfEngine (40 params): 8-track FM-pair sequencer; emits CC
events on CCs {1,2,3,4,5,9,11,12}. Silent audio path.
- AnalysisEngine (0 params, 6 features): port of XiasriAnalysis (pitch
via zero-crossing, aperiodicity via MAD, log-domain energy + attack
derivative + brightness ratio). Inputs to ML on SoundAnalysisMIDI mode.
All param_count() values match schemas/modes/*.json output_size.
4074 LOC total. CMake adds nisps_dsp_engine_tests target with 38
passing tests under -Wall -Wextra -Werror -Wpedantic.
46 lines
1.3 KiB
C++
46 lines
1.3 KiB
C++
// tests/cpp/test_engine_breakor.cpp — sequencer-only engine.
|
|
|
|
#include <array>
|
|
|
|
#include "test_helpers.hpp"
|
|
#include "../../nisps/engines/breakor.hpp"
|
|
|
|
NISPS_TEST(breakor_concept_and_silent_audio) {
|
|
static_assert(nisps::AudioEngine<nisps::BreakOrEngine>);
|
|
NISPS_EXPECT(nisps::BreakOrEngine::param_count() == 56u);
|
|
NISPS_EXPECT(nisps::BreakOrEngine::engine_id() == "breakor");
|
|
|
|
nisps::BreakOrEngine e;
|
|
e.setup(48000.f);
|
|
std::array<float, 56> p;
|
|
for (auto& v : p) v = 0.5f;
|
|
e.set_params(p);
|
|
|
|
// Audio output should be all zeros.
|
|
for (int n = 0; n < 1000; ++n) {
|
|
const auto y = e.process({0.f, 0.f});
|
|
NISPS_EXPECT(y.L == 0.f);
|
|
NISPS_EXPECT(y.R == 0.f);
|
|
}
|
|
}
|
|
|
|
NISPS_TEST(breakor_emits_events_when_playing) {
|
|
nisps::BreakOrEngine e;
|
|
e.setup(48000.f);
|
|
std::array<float, 56> p;
|
|
// Choose params that will trigger (ratios drive the sequencer).
|
|
for (std::size_t i = 0u; i < 56u; ++i) p[i] = 0.5f;
|
|
e.set_params(p);
|
|
e.update_bpm(120.f);
|
|
e.set_playing(true);
|
|
|
|
std::array<nisps::BreakOrEngine::Event, 64> events;
|
|
std::size_t total = 0u;
|
|
for (int n = 0; n < 48000 * 4; ++n) { // 4 seconds
|
|
e.process({0.f, 0.f});
|
|
const std::size_t got = e.pop_events(events);
|
|
total += got;
|
|
if (total > 5u) break;
|
|
}
|
|
NISPS_EXPECT(total > 0u);
|
|
}
|