2727#include " synth/mono_values.h"
2828#include " synth/voice_values.h"
2929#include " remap_functions.h"
30+ #include " resonant_window.h"
3031
3132namespace baconpaul ::six_sines
3233{
@@ -84,6 +85,28 @@ struct alignas(16) OpSource : public EnvelopeSupport<Patch::SourceNode>,
8485 lfoResetMod ();
8586
8687 st.setSampleRate (monoValues.sr .sampleRate );
88+ stWindow.setSampleRate (monoValues.sr .sampleRate );
89+ // Pick the table for the resonant-sweep window. BLACKMAN_HARRIS and TUKEY pull
90+ // their own tables; everything else (closed-form windows + HANN) defaults to
91+ // HANN so a mid-note shape change doesn't read a stale unrelated table — the
92+ // closed-form arms in the inner loop don't read stWindow at all.
93+ {
94+ using RW = Patch::SourceNode::ResonantSweepWindow;
95+ auto rw = static_cast <RW>(
96+ static_cast <uint32_t >(std::round (sourceNode.resonantSweepWindowShape .value )));
97+ switch (rw)
98+ {
99+ case RW::BLACKMAN_HARRIS:
100+ stWindow.setWaveForm (SinTable::BLACKMAN_HARRIS_WINDOW);
101+ break ;
102+ case RW::TUKEY:
103+ stWindow.setWaveForm (SinTable::TUKEY_WINDOW);
104+ break ;
105+ default :
106+ stWindow.setWaveForm (SinTable::HANN_WINDOW);
107+ break ;
108+ }
109+ }
87110 firstTime = true ;
88111 extendedMPrior = sourceNode.extendedModeM .value ;
89112 // Configure the M/N lags only if the operator is actually using an extended mode
@@ -92,12 +115,12 @@ struct alignas(16) OpSource : public EnvelopeSupport<Patch::SourceNode>,
92115 using EM = Patch::SourceNode::ExtendedMode;
93116 auto em = static_cast <EM>(
94117 static_cast <uint32_t >(std::round (sourceNode.extendedModeMode .value )));
95- if (em == EM::PHASE_REMAP)
118+ if (em == EM::PHASE_REMAP || em == EM::RESONANT_SWEEP )
96119 {
97120 extendedLagM.setRateInMilliseconds (10 , monoValues.sr .samplerate , blockSizeInv);
98121 extendedLagM.snapTo (sourceNode.extendedModeM .value );
99- // N is unused in PHASE_REMAP — leave its lag uninitialized until a future
100- // mode that consumes N is added.
122+ // N is unused in PHASE_REMAP / RESONANT_SWEEP — leave its lag uninitialized
123+ // until a future mode that consumes N is added.
101124 }
102125 }
103126 zeroInputs ();
@@ -168,7 +191,7 @@ struct alignas(16) OpSource : public EnvelopeSupport<Patch::SourceNode>,
168191 using EM = Patch::SourceNode::ExtendedMode;
169192 auto em =
170193 static_cast <EM>(static_cast <uint32_t >(std::round (sourceNode.extendedModeMode .value )));
171- if (em == EM::PHASE_REMAP)
194+ if (em == EM::PHASE_REMAP || em == EM::RESONANT_SWEEP )
172195 used = used || (sourceNode.lfoToExtendedModeM .value != 0 );
173196
174197 return used;
@@ -363,23 +386,77 @@ struct alignas(16) OpSource : public EnvelopeSupport<Patch::SourceNode>,
363386 break ;
364387 }
365388 case EM::RESONANT_SWEEP:
366- // coming soon — falls through to the no-extension path for now
367- innerLoopImpl<EM::NONE>(onto, fbv, rf, dRF, phs);
389+ {
390+ using RW = Patch::SourceNode::ResonantSweepWindow;
391+ using RFD = Patch::SourceNode::ResonantSweepFrequencyDepth;
392+ auto rw = static_cast <RW>(
393+ static_cast <uint32_t >(std::round (sourceNode.resonantSweepWindowShape .value )));
394+ auto rfd = static_cast <RFD>(
395+ static_cast <uint32_t >(std::round (sourceNode.resonantSweepFrequencyDepth .value )));
396+ float kScale = 4 .0f ;
397+ switch (rfd)
398+ {
399+ case RFD::TWO:
400+ kScale = 2 .0f ;
401+ break ;
402+ case RFD::FOUR:
403+ kScale = 4 .0f ;
404+ break ;
405+ case RFD::TEN:
406+ kScale = 10 .0f ;
407+ break ;
408+ }
409+ // The PhaseMapShape template arg is unused on this branch; leave it at default.
410+ switch (rw)
411+ {
412+ case RW::SAW:
413+ innerLoopImpl<EM::RESONANT_SWEEP, Patch::SourceNode::PhaseMapShape::SAW, RW::SAW>(
414+ onto, fbv, rf, dRF, phs, kScale );
415+ break ;
416+ case RW::TRIANGLE:
417+ innerLoopImpl<EM::RESONANT_SWEEP, Patch::SourceNode::PhaseMapShape::SAW,
418+ RW::TRIANGLE>(onto, fbv, rf, dRF, phs, kScale );
419+ break ;
420+ case RW::TRAPEZOID:
421+ innerLoopImpl<EM::RESONANT_SWEEP, Patch::SourceNode::PhaseMapShape::SAW,
422+ RW::TRAPEZOID>(onto, fbv, rf, dRF, phs, kScale );
423+ break ;
424+ case RW::FULLTRAP:
425+ innerLoopImpl<EM::RESONANT_SWEEP, Patch::SourceNode::PhaseMapShape::SAW,
426+ RW::FULLTRAP>(onto, fbv, rf, dRF, phs, kScale );
427+ break ;
428+ case RW::HANN:
429+ innerLoopImpl<EM::RESONANT_SWEEP, Patch::SourceNode::PhaseMapShape::SAW, RW::HANN>(
430+ onto, fbv, rf, dRF, phs, kScale );
431+ break ;
432+ case RW::BLACKMAN_HARRIS:
433+ innerLoopImpl<EM::RESONANT_SWEEP, Patch::SourceNode::PhaseMapShape::SAW,
434+ RW::BLACKMAN_HARRIS>(onto, fbv, rf, dRF, phs, kScale );
435+ break ;
436+ case RW::TUKEY:
437+ innerLoopImpl<EM::RESONANT_SWEEP, Patch::SourceNode::PhaseMapShape::SAW, RW::TUKEY>(
438+ onto, fbv, rf, dRF, phs, kScale );
439+ break ;
440+ }
368441 break ;
442+ }
369443 case EM::REDACTED_1:
370444 // coming soon — falls through to the no-extension path for now
371445 innerLoopImpl<EM::NONE>(onto, fbv, rf, dRF, phs);
372446 break ;
373447 }
374448 }
375449
376- template <Patch::SourceNode::ExtendedMode ET,
377- Patch::SourceNode::PhaseMapShape S = Patch::SourceNode::PhaseMapShape::SAW>
378- void innerLoopImpl (float *onto, float *fbv, float rf, const float dRF, uint32_t &phs)
450+ template <
451+ Patch::SourceNode::ExtendedMode ET,
452+ Patch::SourceNode::PhaseMapShape S = Patch::SourceNode::PhaseMapShape::SAW,
453+ Patch::SourceNode::ResonantSweepWindow R = Patch::SourceNode::ResonantSweepWindow::SAW>
454+ void innerLoopImpl (float *onto, float *fbv, float rf, const float dRF, uint32_t &phs,
455+ float kScale = 1 .0f )
379456 {
380457 using EM = Patch::SourceNode::ExtendedMode;
381458 float nextM{0 .f }, dM{0 .f };
382- if constexpr (ET == EM::PHASE_REMAP)
459+ if constexpr (ET == EM::PHASE_REMAP || ET == EM::RESONANT_SWEEP )
383460 {
384461 // Raw target m for this block: patch value + external mod + env / lfo contributions.
385462 auto lfoFac = *lfoFacP;
@@ -412,6 +489,7 @@ struct alignas(16) OpSource : public EnvelopeSupport<Patch::SourceNode>,
412489
413490 auto ph = phs + phaseInput[i] + (int32_t )(feedbackLevel[i] * fb);
414491
492+ float out;
415493 if constexpr (ET == EM::PHASE_REMAP)
416494 {
417495 using PM = Patch::SourceNode::PhaseMapShape;
@@ -428,9 +506,35 @@ struct alignas(16) OpSource : public EnvelopeSupport<Patch::SourceNode>,
428506 else if constexpr (S == PM::DOUBLE_SAW)
429507 ph = remap::remapDoubleSaw (ph & phase::phaseMask, nextM);
430508 nextM += dM;
509+ out = st.at (ph);
510+ }
511+ else if constexpr (ET == EM::RESONANT_SWEEP)
512+ {
513+ using RW = Patch::SourceNode::ResonantSweepWindow;
514+ auto wph = ph & phase::phaseMask;
515+ float window;
516+ if constexpr (R == RW::TRIANGLE)
517+ window = resonant_window::windowTriangle (wph);
518+ else if constexpr (R == RW::TRAPEZOID)
519+ window = resonant_window::windowTrapezoid (wph);
520+ else if constexpr (R == RW::FULLTRAP)
521+ window = resonant_window::windowFullTrapezoid (wph);
522+ else if constexpr (R == RW::HANN || R == RW::BLACKMAN_HARRIS || R == RW::TUKEY)
523+ window = stWindow.at (wph);
524+ else // RW::SAW and any unhandled future value — keep `window` defined.
525+ window = resonant_window::windowSaw (wph);
526+ // Inner phase runs at k(m)x the fundamental and natural-wraps at the
527+ // fundamental boundary via uint32 multiplication (mod phaseMax).
528+ // kScale is the patch-selected sweep depth (2 / 4 / 10).
529+ auto kFactor = kScale * nextM + 1 .0f ;
530+ uint32_t kmph = static_cast <uint32_t >(static_cast <float >(wph) * kFactor );
531+ nextM += dM;
532+ out = window * st.at (kmph);
533+ }
534+ else
535+ {
536+ out = st.at (ph);
431537 }
432-
433- auto out = st.at (ph);
434538
435539 out = out * rmLevel[i];
436540 onto[i] = out;
@@ -506,6 +610,9 @@ struct alignas(16) OpSource : public EnvelopeSupport<Patch::SourceNode>,
506610 }
507611
508612 SinTable st;
613+ // Used by RESONANT_SWEEP for table-based windows (Hann / Blackman-Harris / Tukey).
614+ // Independent of `st` so the operator's main waveform stays selectable freely.
615+ SinTable stWindow;
509616 float fbVal[2 ]{0 .f , 0 .f };
510617};
511618} // namespace baconpaul::six_sines
0 commit comments