Virtual Subharmonic Generator — User Guide

Phantom-bass enhancement by bass-band extraction, tanh harmonic generation, filtered harmonic addition, and optional Mid/Side stereo-width processing.

Author: Shai Cohen Affiliation: Department of Music, Bar-Ilan University, Israel Version: 0.6.1 (2026) License: MIT License Category: Distortion / Math Operations
Contents:

What this does

Virtual Subharmonic Generator is a phantom-bass processor. It does not synthesize new frequencies below the input. Instead, it extracts a low-frequency band, passes that band through an odd-symmetric tanh waveshaper, filters the resulting upper harmonics, and adds those harmonics to a high-passed version of the source.

The perceptual idea is related to residue or “missing-fundamental” pitch: audible upper partials can reinforce the impression of a lower pitch even when a playback system reproduces the fundamental weakly. In this script, however, the original fundamental is not necessarily removed. With typical settings, Highpass_freq lies below Bass_high_freq, so some low-frequency energy remains physically present alongside the generated harmonics.

What “virtual subharmonic” means here

The script creates upper harmonics from bass content; it does not create a true subharmonic at f/2, f/3, or another lower frequency. “Virtual” refers to the intended perceptual bass reinforcement.

Core behavior:

Quick start

  1. Select exactly one Sound object in Praat.
  2. Run Virtual_Subharmonic_Generator.praat.
  3. Choose a preset, or use Custom and set the bass band, Drive, Harmonic_level, M/S switch, and Stereo_width.
  4. Enable Advanced_settings only when you need filter, bass-reference, Side-low-cut, or output-level controls.
  5. Click OK. The output is named originalname_subharm_presetname.
Starting point: Moderate Effect uses the same values as the main-form defaults: 30–120 Hz bass extraction, Drive 3.0, Harmonic_level 0.6, M/S width 0.5, 100-Hz source high-pass, 800-Hz harmonic low-pass, and the 200-Hz Side low-cut.

Processing pipeline

1. Channel preparation

Mono input is converted to stereo. Stereo input is used directly. If the selected Sound has more than two channels, only channels 1 and 2 are processed and the Info window reports how many channels were discarded.

2. Bass extraction

bass = Filter (pass Hann band): Bass_low_freq, Bass_high_freq, 100

The same bass-band limits are used for both channels. Named presets define these limits explicitly.

3. Optional bass-reference normalization

With Preserve bass level, the extracted bass enters the waveshaper at its natural level, so Drive is level-dependent. With Normalize bass before waveshaping, each channel’s extracted bass is scaled to Bass_reference_peak before distortion, then its harmonic branch is divided by the same scale factor after filtering.

referenceScale = Bass_reference_peak / extractedBassPeak bass_for_shaping = bass × referenceScale harmonics_after_filter = harmonics / referenceScale

This makes the nonlinear operating point more consistent across sources of different loudness while restoring the harmonic branch approximately to the source’s original level scale.

4. Harmonic generation

shaped = tanh(bass × Drive) harmonics = Filter (pass Hann band): Bass_high_freq, Harmonic_lowpass, 100

The second filter retains the upper part of the waveshaped bass branch. Because the lower edge is Bass_high_freq, much of the extracted fundamental region is rejected from the harmonic branch.

5. High-pass source and add harmonics

sourceHP = Filter (stop Hann band): 0, Highpass_freq, 100 mixed = sourceHP + harmonics × Harmonic_level

Harmonic_level = 0 therefore returns the high-passed source, not the untouched dry source. Negative Harmonic_level is allowed and adds the harmonic branch with inverted polarity; values above 1 boost it beyond its generated level.

6. Optional M/S width processing

M = (L + R) / 2 S = ((L - R) / 2) × Stereo_width L' = M + S R' = M - S

If Mono_bass_side_lowcut is enabled, the Side signal is high-passed at 200 Hz before reconstruction.

7. Output policy

The stereo result is measured once and processed according to Output_mode. See Output level below.

Controls

Main form

ControlDefaultBehavior
PresetCustomNamed presets overwrite the musical controls and several Advanced filter/Side settings.
Bass_low_freq30 HzLower edge of the extracted bass band. Must be below Bass_high_freq.
Bass_high_freq120 HzUpper edge of the extracted bass band and lower edge of the retained harmonic band.
Drive3.0Multiplies the extracted bass before tanh. Higher values push the branch further into saturation and increase upper-harmonic energy.
Harmonic_level0.6Addition gain for the filtered harmonic branch. Not a dry/wet control. Negative and >1 values are accepted and reported.
Apply_MS_wideningOnEnables the Mid/Side width stage.
Stereo_width0.5Side multiplier: 0 = mono, 1 = identity, <1 = narrower, >1 = wider. Negative values invert Side polarity.
Advanced_settingsOffOpens the secondary dialog for filter/reference/output controls.
Draw_visualizationOnDraws the Praat Picture summary.
Play_resultOnPlays the result when processing finishes.

Advanced settings

ControlDefaultBehavior
Highpass_freq100 HzHigh-pass applied to the original source before harmonic addition. Named presets overwrite it.
Harmonic_lowpass800 HzUpper edge of the retained harmonic band. Named presets overwrite it.
Bass_reference_modePreserve bass levelChooses level-dependent Drive or reference-normalized waveshaping. Remains active with named presets.
Bass_reference_peak0.5Target peak used only when bass-reference normalization is selected.
Mono_bass_side_lowcutOnHigh-passes Side at 200 Hz when M/S processing is active. Named presets overwrite this switch.
Output_modeNormalize to targetSelects the final level policy. Remains active with named presets.
Normalize_target0.95Used only by output modes 2 and 3; must satisfy 0 < target ≤ 1.
Near_silence_dB-80 dBBelow this peak threshold, modes that would normalize skip normalization instead of amplifying numerical residue or near-silence.
Preset/Advanced interaction: Named presets overwrite Highpass_freq, Harmonic_lowpass, and Mono_bass_side_lowcut, as well as the musical controls in the main form. Bass_reference_mode, Bass_reference_peak, Output_mode, Normalize_target, and Near_silence_dB remain active.

Presets

PresetBass bandDriveHarmonic levelHP / Harmonic LPM/SSide low-cut
Custom30–120 Hz3.00.60100 / 800 HzOn, width 0.50On
Subtle Enhancement30–100 Hz2.00.4090 / 600 HzOn, width 0.30On
Moderate Effect30–120 Hz3.00.60100 / 800 HzOn, width 0.50On
Aggressive MaxxBass25–150 Hz5.00.80120 / 1000 HzOn, width 1.30Off
Bass-Centered Narrowing30–110 Hz2.50.50100 / 700 HzOn, width 0.40On
Wide Stereo30–130 Hz3.50.65100 / 900 HzOn, width 1.40Off

The table shows the values that each named preset overwrites. Bass-reference and output-policy settings are intentionally not part of the presets.

M/S stereo processing

Width semantics

M = (L + R) / 2 S = (L - R) / 2 S' = S × Stereo_width L' = M + S' R' = M - S'

Stereo_width = 1 is the identity mapping. Values below 1 narrow the image; values above 1 widen it; 0 collapses the M/S stage to mono. Negative values reverse Side polarity.

200-Hz Side low-cut

When enabled and when M/S processing is active, the script applies:

Side = Filter (stop Hann band): 0, 200, 100

This removes low-frequency stereo-difference information and therefore tends to center the bass. It is not a general “mono compatibility” switch: for this M/S reconstruction, the fold-down identity L' + R' = 2M holds regardless of Stereo_width and regardless of the Side low-cut.

If Apply_MS_widening is off, the Side low-cut is inactive even if its Advanced checkbox is on. v0.6.1 reports this explicitly as inactive (M/S off).

Output level

The output is combined to stereo first, then its absolute peak is measured once. The selected Output mode determines whether that rendered result is left alone, normalized, or attenuated.

ModeExact behavior
Preserve rendered level (safety attenuation only)Leaves the rendered level unchanged when peak ≤ 0.999. If peak > 0.999, globally attenuates to 0.999. It never raises a quiet signal.
Normalize to targetScales every non-near-silent result to Normalize_target. Near-silence is left unchanged.
Attenuate to target only if peak > targetReduces an over-target result to Normalize_target; quieter signals are unchanged. This is conditional global attenuation, not a dynamics limiter.
Legacy (always normalize to 0.95)Reproduces the old fixed 0.95 peak-normalization policy, except that v0.6.1 still protects near-silence from being amplified.

The Info window reports the pre-output peak, the exact output action, the scale factor actually applied, and the final peak.

Visualization

When Draw_visualization is enabled, the 8-inch Praat Picture page contains:

Notes and limitations