206template <
typename SampleType =
float>
228 root_ = ((root_pitch_class % 12) + 12) % 12;
230 int key()
const {
return root_; }
248 if (index < 0 || index >= degree_count_)
return 0;
249 return degrees_[
static_cast<std::size_t
>(index)];
257 if (degree_count_ <= 0)
return out;
262 const int rel = midi_note - root_;
263 const int pc = ((rel % 12) + 12) % 12;
264 const int octave = (rel - pc) / 12;
268 bool chromatic =
true;
269 for (
int i = 0;
i < degree_count_; ++
i) {
270 if (degrees_[
static_cast<std::size_t
>(
i)] == pc) {
276 if (chromatic) degree = snap_degree(pc);
281 root_ + 12 * octave + degrees_[
static_cast<std::size_t
>(degree)];
286 const int target_degree = degree + steps;
287 const int wrapped = floor_div(target_degree, degree_count_);
288 const int index = target_degree - wrapped * degree_count_;
289 out.
target_midi = root_ + 12 * (octave + wrapped) +
290 degrees_[
static_cast<std::size_t
>(index)];
294 out.
clamped = limited != shift;
303 const int midi =
static_cast<int>(
311 static int floor_div(
int a,
int b) {
313 return (
a % b != 0 && ((
a < 0) != (b < 0))) ? q - 1 : q;
321 int snap_degree(
int pc)
const {
322 int lower = degree_count_ - 1;
323 for (
int i = degree_count_ - 1;
i >= 0; --
i) {
324 if (degrees_[
static_cast<std::size_t
>(
i)] <= pc) {
331 const int upper = (lower + 1) % degree_count_;
332 const int upper_pc = degrees_[
static_cast<std::size_t
>(upper)] +
333 (upper == 0 ? 12 : 0);
334 const int down = pc - degrees_[
static_cast<std::size_t
>(lower)];
335 const int up = upper_pc - pc;
336 return up <
down ? upper : lower;
340 const auto index =
static_cast<std::size_t
>(scale_);
341 const std::uint16_t mask =
345 for (
int k = 0;
k < 12; ++
k) {
346 if ((mask >> k) & 1u) {
347 degrees_[
static_cast<std::size_t
>(degree_count_)] = k;
351 if (degree_count_ == 0) {
361 int degree_count_ = 0;
373template <
typename SampleType =
float>
449 drift_[v].set_seed(kDriftSeedBase ^
450 (0x9E3779B9u *
static_cast<std::uint32_t
>(v + 1)));
451 drift_[v].set_depth_percent(0.0);
462 sample_rate_ = (std::isfinite(sample_rate) && sample_rate > 0.0)
465 tracker_.prepare(sample_rate_);
468 auto& shifter = shifter_[
static_cast<std::size_t
>(v)];
469 shifter.prepare(sample_rate_);
471 shifter.set_window_ms(crossfade_ms_);
476 shifter.set_glide_ms(0.0, 0.0);
477 shifter.set_mix(1.0);
478 shifter.set_interp(interp_);
479 glide_[v].prepare(sample_rate_);
480 drift_[v].prepare(sample_rate_);
482 mute_.prepare(sample_rate_);
491 const int worst_shifter_latency =
static_cast<int>(
493 align_.prepare(std::max(tracker_.latency_samples(), worst_shifter_latency) + 1);
501 mute_.set_immediate(0.0);
503 shifter_[
static_cast<std::size_t
>(v)].
reset();
505 shift_semitones_[v] = 0;
506 glide_[v].set_immediate(target_cents(v));
507 apply_ratio(v, glide_[v].value());
514 tracker_.discard_history();
515 align_.discard_history();
517 mute_.set_immediate(0.0);
521 shift_semitones_[v] = 0;
522 glide_[v].set_immediate(target_cents(v));
523 apply_ratio(v, glide_[v].value());
529 void set_key(
int root_pitch_class) { map_.set_key(root_pitch_class); }
532 map_.set_off_scale_policy(policy);
538 if (!valid(voice))
return;
542 int voice_interval(
int voice)
const {
return valid(voice) ? interval_[voice] : 0; }
545 if (!valid(voice) || !std::isfinite(cents))
return;
549 return valid(voice) ? detune_cents_[voice] : 0.0;
553 if (!valid(voice) || !std::isfinite(db))
return;
558 return valid(voice) ? level_db_[voice] :
kLevelMinDb;
562 if (!valid(voice))
return;
563 enabled_[voice] = on;
565 bool voice_enabled(
int voice)
const {
return valid(voice) && enabled_[voice]; }
568 if (!std::isfinite(db))
return;
578 if (!std::isfinite(ms))
return;
580 for (
auto& g : glide_) g.set_time_ms(glide_ms_);
587 if (!std::isfinite(cents))
return;
592 const double epsilon_sigma = humanize_cents_ / kCentsPerUnitEpsilon;
593 for (
auto& d : drift_) d.set_depth_percent(100.0 * epsilon_sigma);
599 if (!std::isfinite(ms))
return;
601 for (
auto& s : shifter_) s.set_window_ms(crossfade_ms_);
608 for (
auto& s : shifter_) s.set_interp(interp);
634 return shifter_[0].latency_samples();
638 bool voiced()
const {
return tracker_.voiced(); }
643 return mapping_[valid(voice) ? voice : 0];
648 return valid(voice) ? glide_[voice].value() : 0.0;
654 return valid(voice) ? shift_semitones_[voice] : 0;
671 if (latency_ <= 0)
return SampleType{0};
673 if (!std::isfinite(
static_cast<double>(x))) {
675 return SampleType{0};
678 const double in =
static_cast<double>(x);
682 if (tracker_.process(dc_.process(
static_cast<SampleType
>(in)))) {
691 align_.push(
static_cast<SampleType
>(in));
692 const double dry =
static_cast<double>(align_.read(latency_));
693 const double shifter_in =
static_cast<double>(align_.read(pre_delay_));
695 const double gate = mute_.process(tracker_.voiced() ? 1.0 : 0.0);
697 double wet_sum = 0.0;
699 if (!enabled_[v])
continue;
703 double cents = glide_[v].process(target_cents(v));
704 if (humanize_cents_ > 0.0)
706 apply_ratio(v, cents);
708 const double wet =
static_cast<double>(
709 shifter_[
static_cast<std::size_t
>(v)].process_wet(
710 static_cast<SampleType
>(shifter_in)));
711 if (gate >
kMuteFloor) wet_sum += level_[v] * gate * wet;
714 return static_cast<SampleType
>(dry_ * dry + wet_sum);
720 static constexpr double kCentsPerUnitEpsilon = 1731.2340490667560888;
723 static constexpr std::uint32_t kDriftSeedBase = 0x48524D4Eu;
725 static bool valid(
int voice) {
return voice >= 0 && voice <
kMaxVoices; }
727 static double clamp_finite(
double v,
double lo,
double hi) {
728 if (!std::isfinite(v))
return lo;
729 return std::clamp(v, lo, hi);
734 double target_cents(
int voice)
const {
735 return 100.0 *
static_cast<double>(shift_semitones_[voice]) +
736 detune_cents_[voice];
739 void apply_ratio(
int voice,
double cents) {
740 shifter_[
static_cast<std::size_t
>(voice)].set_shift_semitones(cents / 100.0);
744 void update_targets() {
745 if (!tracker_.voiced())
return;
747 mapping_[
v] = map_.map_hz(tracker_.f0_hz(), interval_[v]);
748 shift_semitones_[
v] = mapping_[
v].shift_semitones;
752 void update_alignment() {
753 const int tracker_latency = tracker_.latency_samples();
754 const int shifter_latency = shifter_[0].latency_samples();
755 latency_ = std::max(tracker_latency, shifter_latency);
756 pre_delay_ = latency_ - shifter_latency;
760 YinTrackerT<SampleType> tracker_{};
761 DiatonicMapT<SampleType> map_{};
762 PitchShifterT<SampleType> shifter_[
kMaxVoices]{};
763 DelayLineT<SampleType> align_{};
764 DcBlocker<SampleType> dc_{};
765 ConstantTimeSlewLimiterT<double> glide_[
kMaxVoices]{};
766 ConstantTimeSlewLimiterT<double> mute_{};
770 double sample_rate_ = 48000.0;
774 double dry_db_ = 0.0;
778 double detune_cents_[
kMaxVoices] = {0.0, 0.0};
Key + scale interval mapper.
Definition harmony_engine.hpp:207
void set_key(int root_pitch_class)
Root pitch class, 0 = C .. 11 = B.
Definition harmony_engine.hpp:227
void set_off_scale_policy(OffScalePolicy policy)
Definition harmony_engine.hpp:238
OffScalePolicy off_scale_policy() const
Definition harmony_engine.hpp:239
int key() const
Definition harmony_engine.hpp:230
DiatonicMapping map_midi(int midi_note, int interval_steps) const
Maps a chromatic MIDI note.
Definition harmony_engine.hpp:254
static constexpr int kShiftSemitonesMax
Definition harmony_engine.hpp:222
static constexpr int kMaxDegrees
Definition harmony_engine.hpp:211
static constexpr int kIntervalStepsMax
Definition harmony_engine.hpp:215
int degree_semitone(int index) const
Semitone offset of degree index above the root.
Definition harmony_engine.hpp:247
void set_scale(ScaleType scale)
Definition harmony_engine.hpp:232
DiatonicMapping map_hz(double f0_hz, int interval_steps) const
Maps a tracked frequency: rounds to the nearest chromatic note, then map_midi.
Definition harmony_engine.hpp:301
int degree_count() const
Number of degrees in the active scale — 7 for the modes, 5 for the pentatonics.
Definition harmony_engine.hpp:243
ScaleType scale() const
Definition harmony_engine.hpp:236
DiatonicMapT()
Definition harmony_engine.hpp:224
The diatonic harmonizer: YinTrackerT + DiatonicMapT + one PitchShifterT per voice.
Definition harmony_engine.hpp:374
void set_voice_detune_cents(int voice, double cents)
Definition harmony_engine.hpp:544
static constexpr double kLevelMinDb
Definition harmony_engine.hpp:416
static constexpr double kGlideMsMax
Definition harmony_engine.hpp:394
void set_humanize_cents(double cents)
Seeded humanise depth in cents, 1σ.
Definition harmony_engine.hpp:586
static constexpr int kV2IntervalDefault
Definition harmony_engine.hpp:412
static constexpr double kLevelMaxLinear
Definition harmony_engine.hpp:418
int shifter_latency_samples() const
The shifter's own throughput delay, on its own.
Definition harmony_engine.hpp:633
static constexpr double kWorstCaseGain
Definition harmony_engine.hpp:432
double crossfade_ms() const
Definition harmony_engine.hpp:604
void set_key(int root_pitch_class)
Definition harmony_engine.hpp:529
bool voiced() const
Definition harmony_engine.hpp:638
static constexpr double kCrossfadeMsMax
Definition harmony_engine.hpp:388
const DiatonicMapT< SampleType > & diatonic_map() const
Definition harmony_engine.hpp:661
void prepare(double sample_rate)
Sizes the tracker, the alignment delay, and both shifters.
Definition harmony_engine.hpp:461
void set_glide_ms(double ms)
Cents-domain portamento time.
Definition harmony_engine.hpp:577
const DiatonicMapping & voice_mapping(int voice) const
The mapping the engine last computed for a voice, including whether the input was off-scale or the sh...
Definition harmony_engine.hpp:642
double voice_level_db(int voice) const
Definition harmony_engine.hpp:557
static constexpr int kMaxVoices
Definition harmony_engine.hpp:380
double humanize_cents() const
Definition harmony_engine.hpp:595
int voice_shift_semitones(int voice) const
Definition harmony_engine.hpp:653
void set_voice_enabled(int voice, bool on)
Definition harmony_engine.hpp:561
bool voice_enabled(int voice) const
Definition harmony_engine.hpp:565
double voice_ratio(int voice) const
Definition harmony_engine.hpp:650
void set_dry_level_db(double db)
Definition harmony_engine.hpp:567
int voice_interval(int voice) const
Definition harmony_engine.hpp:542
double mute_gain() const
The wet-mute gate, 0 while unvoiced and 1 while voiced.
Definition harmony_engine.hpp:658
int tracker_latency_samples() const
The tracker window W, on its own.
Definition harmony_engine.hpp:630
int latency_samples() const
The delay the whole block imposes, and the delay the dry path is given.
Definition harmony_engine.hpp:627
double dry_level_db() const
Definition harmony_engine.hpp:572
static constexpr double kHumanizeMaxCents
Definition harmony_engine.hpp:407
static constexpr double kGlideMsDefault
Definition harmony_engine.hpp:393
static constexpr double kHumanizeDefault
Definition harmony_engine.hpp:406
const YinTrackerT< SampleType > & tracker() const
Definition harmony_engine.hpp:660
void set_scale(ScaleType scale)
Definition harmony_engine.hpp:530
void set_off_scale_policy(OffScalePolicy policy)
Definition harmony_engine.hpp:531
HarmonyEngineT()
Definition harmony_engine.hpp:441
SampleType process(SampleType x)
Definition harmony_engine.hpp:665
double glide_ms() const
Definition harmony_engine.hpp:582
static constexpr double kMuteFloor
Definition harmony_engine.hpp:439
static constexpr double kLevelMaxDb
Definition harmony_engine.hpp:417
double tracked_f0_hz() const
Definition harmony_engine.hpp:637
void discard_history() noexcept
Constant-time audio fault recovery.
Definition harmony_engine.hpp:513
void set_voice_interval(int voice, int steps)
Voice interval in SCALE STEPS, not semitones.
Definition harmony_engine.hpp:537
static constexpr double kVoiceMuteMs
Definition harmony_engine.hpp:399
static constexpr double kDetuneMaxCents
Definition harmony_engine.hpp:402
static constexpr double kCrossfadeMsDefault
Definition harmony_engine.hpp:386
void set_interp(PitchInterp interp)
Definition harmony_engine.hpp:606
void set_voice_level_db(int voice, double db)
Definition harmony_engine.hpp:552
double voice_detune_cents(int voice) const
Definition harmony_engine.hpp:548
void set_crossfade_ms(double ms)
The shifter's crossfade window, shared by both voices.
Definition harmony_engine.hpp:598
double voice_cents(int voice) const
The voice's post-glide offset in cents, and the ratio derived from it.
Definition harmony_engine.hpp:647
static constexpr int kV1IntervalDefault
Definition harmony_engine.hpp:411
static constexpr double kCrossfadeMsMin
Definition harmony_engine.hpp:387
void reset()
Definition harmony_engine.hpp:497
static constexpr double kDcBlockerPeakGain
The DC blocker's TIME-DOMAIN peak gain: the L1 norm of its impulse response, which is exactly 2 for e...
Definition pitch_shifter.hpp:353
Monophonic fundamental-frequency estimator, after de Cheveigné & Kawahara 2002 [1],...
Definition yin_tracker.hpp:30
Definition custom_node_events.hpp:26
constexpr std::array< ScaleCompatibilityEntry, 10 > kPulpSignalScales
Definition pitch.hpp:184
constexpr std::optional< PitchClassSet > scale_intervals(NamedScale scale) noexcept
Definition pitch.hpp:243
@ index
Definition gpu_pass.hpp:140
T cents_to_ratio(T cents)
Cents → frequency ratio. +100 → 2^(1/12), +1200 → 2.
Definition units.hpp:118
T ratio_to_cents(T ratio)
Frequency ratio → cents. Inverse of cents_to_ratio.
Definition units.hpp:125
T db_to_linear(T db)
Amplitude dB → linear gain. 0 dB → 1, −6.0206 dB → 0.5.
Definition units.hpp:71
T hz_to_midi(T hz)
Hz → MIDI note number.
Definition units.hpp:97
Definition additive_bank.hpp:218
constexpr int kScaleCount
Number of entries in the shipped scale table.
Definition harmony_engine.hpp:155
DiatonicMapT< float > DiatonicMap
Definition harmony_engine.hpp:364
HarmonyEngineT< double > HarmonyEngine64
Definition harmony_engine.hpp:791
@ linear
Definition mod_tools.hpp:106
constexpr std::uint16_t kScaleTable[kScaleCount]
Pitch-class masks, bit k set ⇒ semitone k above the root is in the scale.
Definition harmony_engine.hpp:159
ScaleType
The shipped scale collection, order-locked.
Definition harmony_engine.hpp:141
@ lydian
{0,2,4,6,7,9,11}
Definition harmony_engine.hpp:146
@ minor_pentatonic
{0,3,5,7,10}
Definition harmony_engine.hpp:151
@ natural_minor
Aeolian {0,2,3,5,7,8,10}.
Definition harmony_engine.hpp:143
@ phrygian
{0,1,3,5,7,8,10}
Definition harmony_engine.hpp:145
@ melodic_minor
ascending {0,2,3,5,7,9,11}
Definition harmony_engine.hpp:149
@ dorian
{0,2,3,5,7,9,10}
Definition harmony_engine.hpp:144
@ mixolydian
{0,2,4,5,7,9,10}
Definition harmony_engine.hpp:147
@ harmonic_minor
{0,2,3,5,7,8,11}
Definition harmony_engine.hpp:148
@ major_pentatonic
{0,2,4,7,9}
Definition harmony_engine.hpp:150
@ major
Ionian {0,2,4,5,7,9,11}.
Definition harmony_engine.hpp:142
@ up
Upper sideband on both channels: f → f + Δf.
Definition frequency_shifter_ssb.hpp:315
@ down
Lower sideband on both channels: f → f − Δf.
Definition frequency_shifter_ssb.hpp:316
DiatonicMapT< double > DiatonicMap64
Definition harmony_engine.hpp:365
PitchInterp
Fractional-delay read quality for the two taps.
Definition pitch_shifter.hpp:180
@ linear
2-point. Dattorro's recommendation for modulated delays.
Definition pitch_shifter.hpp:181
HarmonyEngineT< float > HarmonyEngine
Definition harmony_engine.hpp:790
@ a
Definition formant_filter_bank.hpp:20
@ i
Definition formant_filter_bank.hpp:20
@ e
Definition formant_filter_bank.hpp:20
OffScalePolicy
How a pitch class that is not in the active scale is resolved.
Definition harmony_engine.hpp:173
@ nearest_lower
Snap DOWN to the nearest in-scale degree (ties → lower).
Definition harmony_engine.hpp:174
@ nearest
Snap to the nearest in-scale degree in either direction.
Definition harmony_engine.hpp:175
@ mute_wet
Report it and let the caller duck the wet voice.
Definition harmony_engine.hpp:176
@ direct
set_shift_semitones. Detents and the pedal law are bypassed.
Definition pitch_shifter.hpp:194
@ k
Definition input_events.hpp:40
@ v
Definition input_events.hpp:41
The time-domain ratio pitch shifter — the one that warbles.
Deterministic randomness for modulation: a counter-free PRNG, a Gaussian draw, a stateless purpose-ke...
Compatibility include for the canonical modulation-toolkit slew limiter and sample-and-hold implement...
The result of one key+scale interval mapping.
Definition harmony_engine.hpp:181
bool chromatic
The input was off-scale and had to be snapped.
Definition harmony_engine.hpp:187
int shift_semitones
The concrete shift. THIS is the "intelligent" bit.
Definition harmony_engine.hpp:182
int snapped_midi
The in-scale note the input was snapped to.
Definition harmony_engine.hpp:184
bool clamped
The shift hit the ±1 octave ratio clamp.
Definition harmony_engine.hpp:188
int target_midi
The in-scale note the harmony voice plays.
Definition harmony_engine.hpp:185
int degree
Index of snapped_midi in the scale's degree list.
Definition harmony_engine.hpp:186
int input_midi
The chromatic note the tracker was rounded to.
Definition harmony_engine.hpp:183
The real-unit vocabulary shared by every DSP block in the catalog.
Standalone monophonic YIN pitch tracker used by the harmony engine.