Skip to content

tap.pitchaccum~: the shifted line sits beside the intended pitch, not on it #33

Description

@tap

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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions