<feed xmlns='http://www.w3.org/2005/Atom'>
<title>atracdenc/src/transient_detector.cpp, branch new_psy_cont</title>
<subtitle>OpenSource ATRAC1 ATRAC3 Encoder</subtitle>
<id>https://code.mastervirt.ru/atracdenc/atom?h=new_psy_cont</id>
<link rel='self' href='https://code.mastervirt.ru/atracdenc/atom?h=new_psy_cont'/>
<link rel='alternate' type='text/html' href='https://code.mastervirt.ru/atracdenc/'/>
<updated>2026-04-06T21:14:19Z</updated>
<entry>
<title>atrac3: add boundary transient thresholding to prune low-value gain transitions</title>
<updated>2026-04-06T21:14:19Z</updated>
<author>
<name>Daniil Cherednik</name>
<email>dan.cherednik@gmail.com</email>
</author>
<published>2026-04-06T21:10:51Z</published>
<link rel='alternate' type='text/html' href='https://code.mastervirt.ru/atracdenc/commit/?id=10c82ccfa7b802eba5d1366069bc796f5b48d76b'/>
<id>urn:sha1:10c82ccfa7b802eba5d1366069bc796f5b48d76b</id>
<content type='text'>
Problem
Gain curve generation emitted many +/-1 level transitions that do not correspond
to strong local transients. These points consume gain-info bits and can create
low-level modulation artifacts without improving transient handling.

Solution
Introduce explicit transient evidence gating at transition boundaries in
CalcCurve(), and wire it to the existing dynamic min-score path.

What changed
- Added BoundaryTransientScore(env, loc, win):
  - computes local ratio around each subframe boundary
  - R = max(max_right/max_left, max_left/max_right)
  - short symmetric window (win=3 subframes)
- Re-enabled minScore usage in CalcCurve() (previously ignored).
- For each level transition candidate at loc=sf+1:
  - keep unconditionally if loc==targetSf (tail neutral anchor)
  - keep unconditionally if |deltaLevel| &gt;= 2 (strong step)
  - otherwise keep only if BoundaryTransientScore(loc) &gt;= minScore
- Added YAML telemetry:
  - transient_min_score
  - transient_window
  - transition_pruned {loc, delta, score}

Why this is safe
- Strong transitions are preserved.
- Rightmost transition is preserved to keep proper return-to-neutral anchoring.
- Only low-confidence small toggles are removed.

Measured impact (current branch comparison)
Baseline: ea4d33b38 (before this change)
Tracks: show_me_your_spine.wav, 13.wav

Gain-info bits / points:
- spine: 191,697 -&gt; 150,297 bits  (delta -41,400; -21.6%)
         15,593 -&gt; 10,993 points (delta -4,600)
- 13.wav: 1,299,035 -&gt; 979,931 bits  (delta -319,104; -24.6%)
          97,035 -&gt; 61,579 points   (delta -35,456)

Subjective note
User listening reports improved sound and fixes for some low-level artifacts.
</content>
</entry>
<entry>
<title>atrac3: make sticky gain quantization conditional and tune thresholds</title>
<updated>2026-04-06T18:57:40Z</updated>
<author>
<name>Daniil Cherednik</name>
<email>dan.cherednik@gmail.com</email>
</author>
<published>2026-04-06T18:55:17Z</published>
<link rel='alternate' type='text/html' href='https://code.mastervirt.ru/atracdenc/commit/?id=fedaf17422dad9c5a076a6a6c609dfd342f0541e'/>
<id>urn:sha1:fedaf17422dad9c5a076a6a6c609dfd342f0541e</id>
<content type='text'>
Problem
The distribution-aware sticky quantizer reduced gain-curve bitrate, but in some
release/transient frames it over-merged nearby transitions. On spine around
17.657s (ch1/band2), this collapsed the curve shape and could produce an
audible spike.

What changed
- Added frame-level sticky eligibility gating in CalcCurve().
- Sticky is now enabled only when both conditions hold:
  - intra-frame ratio is limited: max_gain / target &lt;= kStickyMaxIntraFrameRatio
  - inter-frame target jump is limited: prev_target / target (symmetric) &lt;= kStickyMaxInterFrameRatio
- Added local uncertainty guard for sticky hold:
  - require idx span from [subframeLow, subframeHigh] quantization to be narrow
    (idxSpan &lt;= 1) before allowing prev-level hold.
- Added YAML diagnostics per band/frame to make gating decisions auditable:
  - sticky_frame_eligible
  - sticky_intra_ratio
  - sticky_inter_ratio

Threshold tuning
Swept candidate pairs on both tracks:
- show_me_your_spine.wav
- 13.wav
Pairs tested:
(5,6), (5,8), (6,8), (6,10), (7,8), (7,10), (8,12)

Selected:
- kStickyMaxIntraFrameRatio = 7.0
- kStickyMaxInterFrameRatio = 10.0

Reason for selection
- Keeps safety behavior on known failure site:
  frame 760, ch1, band2 remains sticky_frame_eligible=false
  and retains non-collapsed curve shape (loc 1,2,5,7).
- Improves gain-modulation bitrate vs previous 6/8 tuning while avoiding fully
  open behavior.

Measured gain-modulation bits (spine + 13.wav)
- 6/8:  1,493,639 bits
- 7/10: 1,490,732 bits  (selected, -2,907 bits vs 6/8)
- 8/12: 1,488,824 bits  (lowest in sweep; not selected to keep extra margin)
</content>
</entry>
<entry>
<title>atrac3: add distribution-aware sticky gain quantization</title>
<updated>2026-04-06T17:16:00Z</updated>
<author>
<name>Daniil Cherednik</name>
<email>dan.cherednik@gmail.com</email>
</author>
<published>2026-04-06T17:14:32Z</published>
<link rel='alternate' type='text/html' href='https://code.mastervirt.ru/atracdenc/commit/?id=af40e881277e429fcabaa694524772480187d555'/>
<id>urn:sha1:af40e881277e429fcabaa694524772480187d555</id>
<content type='text'>
Problem
Gain curve construction still produced many +/-1 level toggles across long runs
(e.g. 7&lt;-&gt;8 chatter). These transitions are usually quantization noise from
subframe-level RMS rounding, not real envelope changes, and they consume gain
bit budget without improving transient protection.

Feature
Introduce distribution-aware sticky quantization for subframe gain levels.
Instead of quantizing only the subframe centre estimate, we also track a robust
within-subframe range and suppress one-step toggles when the previous level is
still consistent with that range.

Implementation
1) AnalyzeGain now optionally returns per-subframe low/high energy estimates
   (robust inter-quantile bounds from micro-chunk analysis inside each subframe).
2) CalcCurve now accepts optional subframe low/high vectors.
3) During sfLevel quantization:
   - compute centre level via RelationToIdx(filtered/target)
   - if new level differs from previous by exactly 1, and previous level is still
     inside [idx(low), idx(high)], keep previous level (sticky hold)
4) CreateSubbandInfo wires the new AnalyzeGain outputs into CalcCurve.
5) Existing point0 guard/boundary logic remains intact; this feature operates
   earlier at sfLevel formation.

Why this is safe
- Only suppresses +/-1 oscillation when previous level is still supported by
  observed subframe distribution.
- Does not clamp large transitions or remove structurally important points.
- Keeps curve scan/priority flow unchanged after sfLevel is formed.

Measured impact on current HEAD (gain-info bits)
Bit accounting uses ATRAC3 gain syntax: per channel header + per band point-count
fields + 9 bits per gain point.

show_me_your_spine.wav:
- base: 219,552 bits (18,688 points)
- with sticky: 172,158 bits (13,422 points)
- saved: 47,394 bits, 5,266 points (-21.59% gain-info bits)

13.wav:
- base: 1,537,724 bits (123,556 points)
- with sticky: 1,146,746 bits (80,114 points)
- saved: 390,978 bits, 43,442 points (-25.43% gain-info bits)
</content>
</entry>
<entry>
<title>atrac3: allow to configure median filter during gain curve calculation</title>
<updated>2026-04-05T18:01:46Z</updated>
<author>
<name>Daniil Cherednik</name>
<email>dan.cherednik@gmail.com</email>
</author>
<published>2026-04-05T18:01:46Z</published>
<link rel='alternate' type='text/html' href='https://code.mastervirt.ru/atracdenc/commit/?id=7bb5b1b5462b5004e89237922843585537bc5aff'/>
<id>urn:sha1:7bb5b1b5462b5004e89237922843585537bc5aff</id>
<content type='text'>
</content>
</entry>
<entry>
<title>atrac3: Prefer largest locations of gain curve points.</title>
<updated>2026-04-05T08:23:54Z</updated>
<author>
<name>Daniil Cherednik</name>
<email>dan.cherednik@gmail.com</email>
</author>
<published>2026-04-05T07:08:53Z</published>
<link rel='alternate' type='text/html' href='https://code.mastervirt.ru/atracdenc/commit/?id=07c2f068c1e468b5beccf45a5e4066cd55b1cbc3'/>
<id>urn:sha1:07c2f068c1e468b5beccf45a5e4066cd55b1cbc3</id>
<content type='text'>
</content>
</entry>
<entry>
<title>atrac3: use in.back() as staircase target instead of nextLevel</title>
<updated>2026-04-05T07:19:40Z</updated>
<author>
<name>Daniil Cherednik</name>
<email>dan.cherednik@gmail.com</email>
</author>
<published>2026-04-02T22:10:08Z</published>
<link rel='alternate' type='text/html' href='https://code.mastervirt.ru/atracdenc/commit/?id=c80ee6a667beb1e7d7aa27f25b96045333090262'/>
<id>urn:sha1:c80ee6a667beb1e7d7aa27f25b96045333090262</id>
<content type='text'>
For non-plateau frames, nextLevel (first lookahead subframe of the next
frame) can be 6× higher than in.back() on release frames.  Using it as
the staircase target caused tail subframes to appear below target →
spurious amplifying points (e.g. {level:7, loc:31}) on release tails,
and underestimated ATT on the peak (33× ratio reduced to 5× because the
wrong target inflated the denominator).

Fix: always use in.back() (actual last subframe of the analysis window)
as the staircase target.  That is where the signal truly returns to
within this frame.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>atrac3: replace CalcCurve with staircase level-scan algorithm</title>
<updated>2026-04-05T07:19:26Z</updated>
<author>
<name>Daniil Cherednik</name>
<email>dan.cherednik@gmail.com</email>
</author>
<published>2026-04-02T21:10:11Z</published>
<link rel='alternate' type='text/html' href='https://code.mastervirt.ru/atracdenc/commit/?id=f0876e929c002fe1adb4ae9a64cb603c8e782439'/>
<id>urn:sha1:f0876e929c002fe1adb4ae9a64cb603c8e782439</id>
<content type='text'>
Replaces the monotone-triplet transient detector with a level-based
staircase scan that builds the gain curve from the target subframe
leftward.  The new algorithm correctly handles rising transients by
attenuating the loud peak region rather than the quiet onset.

Key changes:
- 3-point median filter on gain[] suppresses isolated spikes
- Per-sf level = RelationToIdx(filtered[sf] / target)
- Scan leftward from first-neutral-sf, emit one point per level change
- Priority trim: keep up to 6 points with largest |ΔLevel| first

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>atrac3: fix gc_scale at loc=0 using prev_target/target ratio</title>
<updated>2026-03-29T20:00:41Z</updated>
<author>
<name>Daniil Cherednik</name>
<email>dan.cherednik@gmail.com</email>
</author>
<published>2026-03-29T20:00:41Z</published>
<link rel='alternate' type='text/html' href='https://code.mastervirt.ru/atracdenc/commit/?id=a502947884d16947763c31faf8f572f7c0a8f010'/>
<id>urn:sha1:a502947884d16947763c31faf8f572f7c0a8f010</id>
<content type='text'>
When the first detected transient is at location 0, the CalcCurve loop
computed level = RelationToIdx(in[0]/target).  But in[0] is the ramp
START — for loc=0 there is no pre-ramp region, so in[0] is not the right
amplitude reference for gc_scale, which divides ALL of bufCur (the
previous frame's MDCT window).

The external point0 block derives its formula as:
  hpfRmsNextMod = mean(gain[0..loc-1]) / GainLevel[pts[0].Level] ≈ target
  point0Level   = RelationToIdx(prevTarget / hpfRmsNextMod) ≈ prevTarget/target

For loc=0, hpfRmsNextModValid is false and the block cannot fire.
Override curve[0].Level inside CalcCurve with RelationToIdx(savedPrevTarget/target)
— consistent with the loc&gt;0 formula and correctly bridges cross-frame amplitude.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>atrac3: point0 in HPF domain using prevTarget/hpfRmsNextMod; enable band 2</title>
<updated>2026-03-29T19:12:00Z</updated>
<author>
<name>Daniil Cherednik</name>
<email>dan.cherednik@gmail.com</email>
</author>
<published>2026-03-29T19:12:00Z</published>
<link rel='alternate' type='text/html' href='https://code.mastervirt.ru/atracdenc/commit/?id=d187c6ec6b231196dead559f1c80cf86ef92c5f2'/>
<id>urn:sha1:d187c6ec6b231196dead559f1c80cf86ef92c5f2</id>
<content type='text'>
Point0 calculation switched from raw-PCM RMS ratio to HPF-filtered domain:
- prevTarget (stored as ctx.LastTarget from previous CalcCurve call) replaces rmsCur
- hpfRmsNextMod = mean(gain[0..loc-1]) / GainLevel[pts[0].Level] replaces rmsNextMod
  (only the pre-ramp constant-level zone, same domain as gain[])
- TCurveBuilderCtx gains LastTarget field; CalcCurve stores target before returning

Also extends gain control to band 2 (~11–16 kHz) by changing the skip threshold
from band &gt;= 2 to band &gt;= 3.  Perceptually sounds better; regression metrics
worsen due to broadband measurement not capturing per-band HF improvement.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>atrac3: log plateau result and target source inside CalcCurve</title>
<updated>2026-03-26T14:13:28Z</updated>
<author>
<name>Daniil Cherednik</name>
<email>dan.cherednik@gmail.com</email>
</author>
<published>2026-03-26T14:13:28Z</published>
<link rel='alternate' type='text/html' href='https://code.mastervirt.ru/atracdenc/commit/?id=b533dc8947ab6ec4a7e708f597eccc2f7a4a21d9'/>
<id>urn:sha1:b533dc8947ab6ec4a7e708f597eccc2f7a4a21d9</id>
<content type='text'>
Pass yamlLog into CalcCurve so plateau_level, plateau_max_raw,
plateau_release, and target/source are emitted directly from the
function that computes them, instead of via stale TCurveBuilderCtx
fields. Remove LastTarget and LastTargetFromPlateau from the context
struct entirely.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt;
</content>
</entry>
</feed>
