Recorded while measuring scrub.h's transposition against a second shifter. It is a real, reproducible observation about a shipped object, and it deserves its own investigation rather than riding along on an unrelated kernel.
It is also a partial retraction. book/PLAN-radiohead-family.md previously recorded a "near-total cancellation" in this object — band energy 0.004 at 311 Hz +19 semitones. That number was an artifact of my own metric and does not survive rechecking; see the last section. The observations below are what is left after the metric was fixed, and they are smaller but real.
What is observed
pitchaccum::accum_bank at defaults (window 87 ms, xfade 50 %), mix 100, both voices transposed together, driven with a 0.5-amplitude sine, measured over the last third of 3 s.
1. Band energy retained, against a perfect shifter. Energy in a band around the transposed pitch, relative to the same band of an ideal transposed sine. Band is max(15 Hz, 3 % of the target) — wide enough in cents at every pitch:
|
mean |
worst |
pitchaccum::accum_bank |
0.907 |
0.633 (220 Hz, +19 st) |
scrub::machine (same sweep) |
0.988 |
0.917 |
That gap is not by itself a bug — this is a two-tap crossfade shifter, and the object's job is shimmer rather than transparent transposition. It is the context for the two below.
2. The strongest spectral line is not at the intended pitch. Scanning 50–2000 Hz in 1 Hz steps over the last second:
| input |
interval |
intended |
strongest line |
| 110 Hz |
+12 |
220.0 |
225 |
| 220 Hz |
+12 |
440.0 |
450 |
| 220 Hz |
+19 |
659.3 |
679 |
| 220 Hz |
+18 |
622.3 |
640 |
For ratio 2 the offset is about 5 Hz, which is close to half the crossfade rate (r−1)/window = 11.5 Hz — consistent with the two-tap crossfade amplitude-modulating the output and a sideband exceeding the carrier. The +19 offsets do not fit that arithmetic as cleanly, so the explanation is a hypothesis rather than a finding.
3. The YIN oracle disagrees with the intended pitch at two intervals. tap::dsp::yin on the same renders, in cents from intended:
| input |
−12 |
−7 |
−3 |
+3 |
+7 |
+12 |
+19 |
| 110 Hz |
−0.5 |
0.0 |
0.0 |
0.0 |
+35.1 |
0.0 |
+35.5 |
| 155.6 Hz |
0.0 |
0.0 |
0.0 |
0.0 |
+23.4 |
0.0 |
+24.4 |
| 220 Hz |
0.0 |
0.0 |
0.0 |
0.0 |
+8.8 |
+0.1 |
+10.3 |
| 311 Hz |
0.0 |
0.0 |
0.0 |
0.0 |
+3.0 |
+0.1 |
+3.5 |
| 440 Hz |
0.0 |
0.0 |
0.0 |
+0.1 |
−4.8 |
+0.1 |
−5.0 |
Five of the seven intervals are exact to within a cent at every fundamental. The two that are not — +7 and +19 — are the two whose ratios are near 3/2 and 3 without being either. The error falls with fundamental, which is the signature of a period-resolution artifact in the detector as much as of a real mistuning, so this is ambiguous and needs deciding, not assuming.
What to decide
- Is (3) a real pitch error in
transposer at non-power-of-two ratios, or YIN locking to sideband-dominated material? A second, independent estimator on the same renders settles it. If it is real, +35 cents at 110 Hz is audible and matters.
- Is (2) worth fixing, or is it the object? A two-tap crossfade shifter has this sideband structure by construction;
tt_shift/tap.shift~ inherited the same 87 ms window. If the sidebands are the character, the honest move is to state them in the header rather than chase them.
- Either way the header currently promises nothing about transposition accuracy. It should say what was measured.
Reproduction
Standalone, no test harness. Build against the kernel and tap::dsp:
g++ -std=c++20 -O2 -Iinclude -Isubmodules/dsptap/include pa_yin.cpp -o pa_yin
tap::tools::pitchaccum::accum_bank bank;
bank.prepare(48000.0);
bank.set_mix(100.0);
bank.set_trans(0, st);
bank.set_trans(1, st);
bank.snap();
// drive with 0.5 * sin(2*pi*f0*t) for 2 s, run tap::dsp::yin over the second half
Fundamentals 110 / 155.6 / 220 / 311 / 440 Hz × intervals −12 / −7 / −3 / +3 / +7 / +12 / +19 semitones.
The retraction, in full
The original sweep measured energy in a fixed ±15 Hz band around the transposed pitch. ±15 Hz is about 115 cents wide at 220 Hz but only about 26 cents at 932 Hz, so at high transposed pitches the band was narrower than the shifter's own spread and simply missed the energy. That produced two readings of 0.0001 and 0.0006, which were recorded as near-total cancellations. Widening the band to 3 % — the same width in cents everywhere — moves those two points to 0.63 and 0.85, and no cancellation exists anywhere on the sweep.
This is exactly the error book/src/machine/scrub.md warns about under "Measure the band, not the bin", committed one section after writing the warning: if the process can smear or shift a partial, the probe has to be wide enough in the units the process works in. For a pitch shifter that unit is cents, never hertz.
book/PLAN-radiohead-family.md and book/src/scrub.md are being corrected alongside this issue.
Recorded while measuring
scrub.h's transposition against a second shifter. It is a real, reproducible observation about a shipped object, and it deserves its own investigation rather than riding along on an unrelated kernel.It is also a partial retraction.
book/PLAN-radiohead-family.mdpreviously recorded a "near-total cancellation" in this object — band energy 0.004 at 311 Hz +19 semitones. That number was an artifact of my own metric and does not survive rechecking; see the last section. The observations below are what is left after the metric was fixed, and they are smaller but real.What is observed
pitchaccum::accum_bankat defaults (window 87 ms, xfade 50 %),mix 100, both voices transposed together, driven with a 0.5-amplitude sine, measured over the last third of 3 s.1. Band energy retained, against a perfect shifter. Energy in a band around the transposed pitch, relative to the same band of an ideal transposed sine. Band is
max(15 Hz, 3 % of the target)— wide enough in cents at every pitch:pitchaccum::accum_bankscrub::machine(same sweep)That gap is not by itself a bug — this is a two-tap crossfade shifter, and the object's job is shimmer rather than transparent transposition. It is the context for the two below.
2. The strongest spectral line is not at the intended pitch. Scanning 50–2000 Hz in 1 Hz steps over the last second:
For ratio 2 the offset is about 5 Hz, which is close to half the crossfade rate
(r−1)/window = 11.5 Hz— consistent with the two-tap crossfade amplitude-modulating the output and a sideband exceeding the carrier. The +19 offsets do not fit that arithmetic as cleanly, so the explanation is a hypothesis rather than a finding.3. The YIN oracle disagrees with the intended pitch at two intervals.
tap::dsp::yinon the same renders, in cents from intended:Five of the seven intervals are exact to within a cent at every fundamental. The two that are not — +7 and +19 — are the two whose ratios are near 3/2 and 3 without being either. The error falls with fundamental, which is the signature of a period-resolution artifact in the detector as much as of a real mistuning, so this is ambiguous and needs deciding, not assuming.
What to decide
transposerat non-power-of-two ratios, or YIN locking to sideband-dominated material? A second, independent estimator on the same renders settles it. If it is real, +35 cents at 110 Hz is audible and matters.tt_shift/tap.shift~inherited the same 87 ms window. If the sidebands are the character, the honest move is to state them in the header rather than chase them.Reproduction
Standalone, no test harness. Build against the kernel and
tap::dsp:Fundamentals 110 / 155.6 / 220 / 311 / 440 Hz × intervals −12 / −7 / −3 / +3 / +7 / +12 / +19 semitones.
The retraction, in full
The original sweep measured energy in a fixed ±15 Hz band around the transposed pitch. ±15 Hz is about 115 cents wide at 220 Hz but only about 26 cents at 932 Hz, so at high transposed pitches the band was narrower than the shifter's own spread and simply missed the energy. That produced two readings of 0.0001 and 0.0006, which were recorded as near-total cancellations. Widening the band to 3 % — the same width in cents everywhere — moves those two points to 0.63 and 0.85, and no cancellation exists anywhere on the sweep.
This is exactly the error
book/src/machine/scrub.mdwarns about under "Measure the band, not the bin", committed one section after writing the warning: if the process can smear or shift a partial, the probe has to be wide enough in the units the process works in. For a pitch shifter that unit is cents, never hertz.book/PLAN-radiohead-family.mdandbook/src/scrub.mdare being corrected alongside this issue.