Tester feedback asked for a threshold and a ratio. Each band now cuts like a small compressor: Threshold (dB above the local floor) replaces Selectivity, Ratio scales the cut per dB above it (inf:1 is the old behaviour), and a Soft/Hard knee switch sets the curve. A small graph shows bump height in, cut out. Mix and Output move to the slider panel so the detection panel fits. common-ui gains rotary_text for knobs with custom value text.
166 lines
8.4 KiB
Markdown
166 lines
8.4 KiB
Markdown
# Patina
|
||
|
||
Patina smooths a sound’s rough surface without erasing its character. Formerly
|
||
**Resonance Compressor**; current version **0.4.0** (0.5.0 unreleased).
|
||
|
||
A mixing effect that finds harsh, ringing regions as they happen and pulls them
|
||
down, while plain harmonic tone passes. Independently designed in Rust; no claim
|
||
of matching Soothe or Quance.
|
||
|
||

|
||
|
||
## Install on Windows
|
||
|
||
Download the Windows artifact from the Gitea plugin's Gitea release, or the branch pipeline's **windows** job and
|
||
extract the ZIP. Copy `Patina.clap` into
|
||
`C:\Program Files\Common Files\CLAP\`. In FL Studio 2024.1 or newer, open
|
||
Options → Manage plugins, enable Verify plugins, and Find installed plugins.
|
||
Load Patina in a mixer effect slot. Keep the font license with
|
||
redistributed packages.
|
||
|
||
## Work with it
|
||
|
||
Each band works like a small compressor. It compares its level with the local floor
|
||
around it and cuts only the part that sticks out:
|
||
|
||
- **Depth** is the largest cut per band, in dB. 0 dB does nothing.
|
||
- **Sharpness** sets what counts as a resonance. Low values follow broad,
|
||
smooth bumps (honk, boxiness). High values target narrow rings and whistles.
|
||
- **Threshold** is how far (dB) a band must rise above its neighbours before it
|
||
is cut. Low values treat any bump; high values treat only the worst.
|
||
- **Ratio** is how hard a bump above the threshold is cut. At n:1 a bump ends
|
||
1/n as far above the threshold as it started. Low ratios leave small bumps
|
||
nearly alone and still cut big ones; ∞:1 flattens every bump to the
|
||
threshold (this is how Patina worked before 0.5).
|
||
- **Knee**: Soft fades cuts in around the threshold. Hard starts them right at
|
||
the threshold and uses half the Attack time.
|
||
|
||
The small graph under the knobs shows the result: bump height above the floor
|
||
in, cut out, with the threshold and the Depth limit.
|
||
|
||
Start with Depth around 6 dB. Raise Threshold until only the harsh parts show
|
||
cuts, then use Delta to hear what is removed. To hit big resonances hard and
|
||
leave small ones alone, raise Depth to 12–20 dB and set Ratio to 2:1–4:1.
|
||
Restrict Low/High frequency to keep the source’s body out of reach. Attack and
|
||
Release set how quickly cuts grow and recover; Transient guard briefly relaxes
|
||
cuts on attacks to keep punch.
|
||
|
||
### Tone guard for bass lines
|
||
|
||
On a bass line, a bump can mean two things. A resonance fixed in Hz (a formant,
|
||
a comb or filter ring) stays at the same frequency on every note: that is what
|
||
Patina should cut. A bump that follows the note (a harmonic the patch is built
|
||
on, or a Lathe boost) moves with every note: cutting it changes the patch
|
||
differently on each note.
|
||
|
||
**Tone guard** tells them apart. Patina detects the played note (30–500 Hz)
|
||
and remembers, over about four seconds, how high each bump was in Hz and how
|
||
high it was relative to the note. A bump that has stayed with the note more
|
||
than in Hz gets less cut; at 100 % none. A resonance fixed in Hz is cut as
|
||
before. It needs a few different notes before it can decide: on one held note
|
||
both look the same and Patina cuts as normal. The **NOTE** readout shows what
|
||
Patina follows, and a blue strip on top of the plot shows where cuts are held
|
||
back. Start from the **Bass line** preset. At 0 % Tone guard is off.
|
||
|
||
Measured on a saw line F1–F2 with Low at 150 Hz: a +12 dB boost on harmonic 6
|
||
is cut by 5.8 dB without Tone guard and by 0.4 dB at 100 %; a formant at 1.5 kHz
|
||
is cut by 5.8 dB either way.
|
||
|
||
Double-click the plot to add one of four focus points. Drag a point up to cut
|
||
more in that region, down to protect it. Wheel changes its width. Range handles
|
||
on the plot drag the processing limits.
|
||
|
||
On the plot, the grey fill is the measured input. The bright line is what the
|
||
detector hears (its smoothed spectral envelope); the dim line is what it expects
|
||
there. Accent-coloured cuts appear where the bright line rises above the dim one
|
||
by more than Threshold allows. The blue curve is the focus/protect shape.
|
||
|
||
Delta plays delayed dry minus wet before output trim. Use it to inspect changes,
|
||
then return to normal monitoring to decide whether the music improved.
|
||
Mix is a linear blend of aligned signals. Bypass keeps the same latency.
|
||
Use Output for matched-level comparison. Spectral attenuation is not a limiter:
|
||
sample peaks can increase even when frequency gains never exceed unity.
|
||
|
||
Four presets and A/B comparison help explore alternatives. Host parameters and
|
||
window size persist; Delta does not.
|
||
|
||
## How detection works
|
||
|
||
1. Each analysis frame is grouped into 512 log-spaced bands. Each band holds the
|
||
mean power of the FFT bins it covers, smoothed over time (about 5 ms rise,
|
||
40 ms fall) so cuts hold on a stable ring instead of flickering.
|
||
2. Neighbouring peaks are joined in dB, so the gaps between harmonics do not count
|
||
as the expected level. A plain harmonic tone reads as a smooth envelope.
|
||
3. The envelope is smoothed across frequency by the Sharpness width.
|
||
4. The reference is a robust local straight-line fit over 2–3 octaves: spectral
|
||
tilt is expected, bumps cannot raise their own reference, and empty regions
|
||
(below a fundamental, beyond a filter cutoff) are ignored.
|
||
5. Envelope minus reference is the band's height above its floor. Above
|
||
Threshold it becomes a cut through the Knee and Ratio curve, limited by
|
||
Depth, smoothed with Attack/Release, and linked across stereo.
|
||
|
||
## Timing and limits
|
||
|
||
Mixing only: 2048-sample analysis windows at 44.1/48 kHz, 4096 at 88.2/96 kHz,
|
||
8192 at 176.4/192 kHz, with eight overlaps. Delay is one window, about 43–46 ms.
|
||
The plugin reports it for host compensation. Supported layouts are mono and
|
||
linked stereo; supported rates are those six standard rates.
|
||
|
||
The engine uses complex RustFFT with supplied scratch buffers, WOLA reconstruction,
|
||
the envelope/reference detector above, bounded soft-knee cuts, and temporal
|
||
smoothing. Original bin phases are retained. This is not a minimum-phase filter
|
||
bank, a live monitoring effect, a limiter, or an automatic separation of tone and
|
||
unwanted resonance. No external sidechain, Learn or M/S. The pitch detector
|
||
serves Tone guard only.
|
||
|
||
## Build and offline evaluation
|
||
|
||
```sh
|
||
cargo test --workspace --locked
|
||
cargo run --release -p patina-render -- input.wav processed.wav
|
||
cargo run --release -p patina-render -- input.wav delta.wav --delta
|
||
cargo run --release -p patina-render -- --demo /tmp/patina-demo.wav
|
||
cargo run --release -p patina-render -- --benchmark
|
||
python scripts/build-plugin.py --plugin patina --target x86_64-pc-windows-gnu
|
||
```
|
||
|
||
The renderer accepts mono/stereo PCM16/24/32 and float32 RIFF/WAVE, capped at
|
||
256 MiB; WAV extensible/RF64 are not accepted. It writes float32 WAV, compensates
|
||
the processing delay, retains original duration, and refuses existing output.
|
||
An optional settings JSON uses the core's Settings structure.
|
||
|
||
## CI and acceptance
|
||
|
||
Gitea Actions cross-builds Windows binaries with MinGW on a Docker-capable
|
||
runner labelled `ubuntu-latest`. Branch/PR workflows build a combined preview
|
||
bundle; `patina-v<version>` tags check, build, and publish Patina independently.
|
||
See [release workflow](../releases.md) for runner setup and release attachments.
|
||
Actual runner execution remains to be verified on the Gitea instance.
|
||
|
||
Before calling host support complete, test FL Studio discovery as an effect,
|
||
automation, plugin delay compensation, parallel dry/wet alignment, repeated loading,
|
||
project restoration, mono/stereo, all supported sample rates, and render tails.
|
||
Judge through real patches with matched listening levels: preserve vowel motion,
|
||
snare punch, bass body, and stereo placement. Synthetic tests do not establish
|
||
artistic usefulness.
|
||
|
||
Local benchmark, optimized build, stereo 48 kHz, FFT 2048/hop 256: five seconds
|
||
processed in about 0.35–0.45 seconds (7–9% of one core on a laptop CPU). This is
|
||
not a worst-case multi-instance guarantee.
|
||
|
||
|
||
## Existing projects and source layout
|
||
|
||
The CLAP ID is now `io.narl.bass-foundry.patina` (earlier builds used
|
||
`io.storyboard.resonance-compressor`). Hosts treat Patina as a new plugin, so
|
||
add it again in projects saved with an earlier build. Version 0.2 replaced
|
||
Amount, Sensitivity, Max cut, Neighborhood, Cut width, and Knee with Depth,
|
||
Sharpness, and Selectivity; version 0.5 replaced Selectivity with Threshold
|
||
(dB), Ratio and Knee, so projects saved earlier load Threshold at its default.
|
||
Delete the old plugin file, place `Patina.clap`, and
|
||
rescan FL Studio.
|
||
|
||
Source is grouped into `plugins/patina/{core,ui,render,plugin}`. See
|
||
[research](research.md), [development](../development.md), and the
|
||
[independent release workflow](../releases.md).
|