Skip to content

Spectral Editing ​

spectralEdit(...) is libsonare's region-based STFT editor. Use it to change a time and frequency rectangle inside a mono clip: attenuate a whistle, mute a short hum, boost a narrow band, or heal a small dropout from neighboring frames.

STFT (short-time Fourier transform) is the time-frequency grid a signal is cut into so it can be edited by frequency; the glossary explains it.

It is an offline transform. The output keeps the same sample rate and length as the input, and each region operation is applied in order to the same STFT buffer.

SPECTRAL EDIT · REGIONIDLE
Spectral editing — erase one region, leave the rest

A narrow whistle is added to the middle of a pad. Edit its time–frequency region with spectralEdit and compare attenuate, mute and heal. The selected STFT bins change; overlapping analysis windows and reconstruction can spread changes near region boundaries. Heal estimates the content from neighbouring frames rather than recovering the original lost signal. NOTCH shows the in-band reduction in dB.

Compare
Mode

When To Use It ​

GoalMode
Turn a selected band up or down by an exact amountgain
Reduce a selected band — same formula as gain, conventionally passed a negative gainDbattenuate
Zero every bin inside the rectanglemute
Fill a small artifact from nearby framesheal

gain and attenuate run the identical math; they exist as two names so your code reads by intent. Use gain when you mean to boost and attenuate (with a negative gainDb) when you mean to cut.

Use Editing DSP for pitch, time, note, and voice edits. Use Mastering Processors for whole-track tone, dynamics, repair, and delivery processing.

Usage ​

typescript
import { init, spectralEdit } from '@libraz/libsonare';

await init();

const repaired = spectralEdit(samples, sampleRate, [
  {
    startSample: Math.round(1.25 * sampleRate),
    endSample: Math.round(1.55 * sampleRate),
    lowHz: 7600,
    highHz: 8300,
    mode: 'attenuate',
    gainDb: -18,
  },
  {
    startSample: Math.round(2.1 * sampleRate),
    endSample: Math.round(2.18 * sampleRate),
    lowHz: 0,
    highHz: 400,
    mode: 'heal',
  },
], {
  nFft: 2048,
  hopLength: 512,
  window: 'hann',
  healRadiusFrames: 2,
});
python
import libsonare as sonare

repaired = sonare.spectral_edit(
    samples,
    sample_rate,
    [
        sonare.SpectralRegionOp(
            start_sample=int(1.25 * sample_rate),
            end_sample=int(1.55 * sample_rate),
            low_hz=7600,
            high_hz=8300,
            gain_db=-18,
            mode="attenuate",
        ),
        sonare.SpectralRegionOp(
            start_sample=int(2.1 * sample_rate),
            end_sample=int(2.18 * sample_rate),
            low_hz=0,
            high_hz=400,
            mode="heal",
        ),
    ],
    n_fft=2048,
    hop_length=512,
    window="hann",
    heal_radius_frames=2,
)

A region given in seconds:

typescript
const muted = spectralEdit(samples, sampleRate, [
  { startSec: 0.5, endSec: 1.5, lowHz: 0, highHz: 0, mode: 'mute' },
]);

Options ​

STFT · SPECTRALIDLE
STFT — seeing time and frequency at once

A tone gliding from 400 Hz to 1 kHz, with four clicks. Each column is one short-time spectrum; brighter means more energy. Change nFft: at 256 the clicks are thin lines and the tone is a band about 170 Hz wide; at 4096 the tone is a line about 20 Hz wide and each click smears across about 280 ms.

nFft (window)

You give the region in samples and hertz, but the edit lands on the STFT grid those two options build:

What nFft and hopLength build
input samplesnFft — window lengthwindowed frameshopLengthspectrogram columnsone FFT per frame → one column…
  • windowed frames
  • the same samples, analysed again
  • spectrogram columns
nFft is the length of one analysis window and hopLength is how far the next one starts after it, so at the defaults every window overlaps the previous one by three quarters. Each window becomes one spectrogram column — the vertical strips your time range is applied to, sliced into frequency bins your lowHz/highHz range picks from.
FieldMeaning
startSample / endSampleTime rectangle in input samples; the start defaults to 0. Omit endSample (Python: leave end_sample as None) to span to the end of the signal. endSample: 0 is an empty region, not the full signal, on every surface. A negative position is refused (RangeError in JavaScript, SonareValueError in Python); -1 is not a "to the end" sentinel.
startSec / endSecThe same bounds in seconds (Python start_sec / end_sec), rounded to the nearest sample at the call's sample rate. Give one spelling per bound; both startSample and startSec (or both end spellings) is refused.
lowHz / highHzFrequency rectangle in Hz. Values are clamped to [0, Nyquist]; highHz <= 0 means Nyquist.
gainDbGain in dB. Applied identically by gain and attenuate as magnitude *= 10^(gainDb/20); pass a negative value to reduce. Ignored by mute/heal.
nFftPower-of-two FFT size in [4, 262144], default 2048. Any other positive value is refused with an error naming nFft and the rule. In JavaScript, 0 or omission keeps the default; Python refuses 0.
hopLengthSTFT hop, default 512; must satisfy 0 < hopLength <= nFft / 2 (JavaScript reads 0 as the default).
windowhann, hamming, blackman, or rectangular (Python also accepts rect).
healRadiusFramesNeighbor frames on each side used by heal, default 2; 1 or more (0 keeps the default, negatives are refused).

Resolution and edges ​

The rectangle is applied to whole STFT cells, so its sharpness is set by nFft:

nFft at 48 kHzBin spacingWindow lengthGood for
102446.9 Hz21 msShort clicks and fast events
2048 (default)23.4 Hz43 msGeneral use
409611.7 Hz85 msA steady narrow tone, such as a whistle or hum
  • Frequency edges. The bins between lowHz and highHz change and the bins outside do not, with no taper between them. A tone sitting right at an edge spreads over neighboring bins (window leakage), so part of it can survive a mute. Leave a margin of a few bins around the band you want to remove.
  • Time edges. The frames edited are those whose centers fall in the time range, rounded down to the hopLength grid, but each frame covers nFft samples. After resynthesis the change therefore fades in and out over about half a window on each side (about 21 ms at the defaults) instead of starting on the exact sample.
  • What heal does. For each bin in the rectangle, heal averages the magnitude of up to healRadiusFrames unedited frames on each side, ramps between those two levels across the gap, and continues the phase from the last frame before the gap at the rate that bin's phase was already advancing (from the first frame after it when the gap starts the signal). A steady tone runs through the gap; anything that was only inside the gap, such as a click, is gone.

Small regions usually work best. Broad, long, or repeatedly healed regions can sound phasey because the edit happens in an STFT representation rather than a source-separated model.