Repository navigation
Normalize a slot: measure before asking, and ask only when there is something to do - #158
Closed
AliceLafox wants to merge 15 commits into
Closed
AliceLafox wants to merge 15 commits into
AliceLafox wants to merge 15 commits into
Conversation
… the job can speak its words The single-slot Normalize is to decide before it asks (#142), and what it decides must be what commands::normalize would do with the same take. The target window, the damaged and the unmeasurable refusals, and the 1e-9 dB under which a capped boost counts as none were written inline in the command; they are now requireNormalizeTarget, normalizeDamagedRefusal, normalizeUnmeasurableRefusal and loudness::kNoGainDb, and the command calls them. Its behaviour and its sentences are unchanged.
…hing, or refuse in its words
normalizeplan::decide (app/NormalizePlan.h) is the command's rule over a
wav::LoudnessReading and a target, pure and JUCE-free: garbage first, then
silence, then the kAlreadyAtTargetLu tolerance, then the gain rule under
the -1 dBTP ceiling. "apply" carries the window's first line ("Measured
-22.7 LUFS, this adds +4.7 dB", or "+2.1 dB possible: the -1 dBTP ceiling
stops the rest" when capped), "nothingToDo" carries the cap as its reason,
"refuse" carries the command's own sentence.
normalize_plan_tests holds it to the tolerance from both sides, the cap,
the refusals and the target window, then runs plan and command side by
side over eight synthetic takes - at target, peaky, faint, short, damaged,
quiet, capped, loud - where every write, every "nothing" and every refusal
must match, sentence for sentence.
recallOrReadSlotLoudness, for Normalize's first step (#142), reads the slot's take, hashes it and asks the store for a reading filed under that hash (#140); only bytes it has never measured are measured, and that reading is filed as readSlotLoudness files it. The key is the content hash, so a reading inferred from a take's name, size and date (#141) never answers here. With no session open for the card the store is not opened for the asking; a store that cannot answer is measured around, with the trouble in `failure`. The recorder suite plants a reading no meter would take off the take and gets it back, not dated anew; swaps another sound in under the same name, size and date as the snapshot's row and gets it measured; and shows the step leaves the card's timeline as it was for a take with nothing to do, while the normalize that follows a take with something to do adds exactly one row.
… is something to do Normalize on one slot opened its window first and measured inside the job, so a take already at the target was approved, left alone, and still left a row in the history saying why (#142). The action now runs a read-only background job first - no operation, no archive, no journal - that recalls or measures the take and asks normalizeplan::decide. Nothing to do: a toast in describeReading's words, and nothing else. A take the command would refuse: the error banner with the command's sentence, where the job's failure used to land - the fix for a damaged take is a re-push, and its reason has to outlive the click. Something to do: the window opens with the numbers in hand. The job still measures the card's bytes before it writes, whatever the first step believed. The right-click item and the player's Normalize button both come through here; the bulk apply is untouched.
… when it would write
kNoGainDb was 1e-9 dB, far below anything a float32 sample carries. A take Normalize had capped lands a few hundredths of a dB under the ceiling once its samples are float32 again, so the next Normalize offered "+0.0 dB possible", rewrote the same bytes and added a history row - every time; a take with 0.049 dB of headroom opened the window too (review of #142, P0). The smallest gain worth writing is now loudness::kSmallestGainDb, the same audible step as kAlreadyAtTargetLu. commands::normalize and normalizeplan::decide write nothing under it, and the LUFS column's attention follows the same line, so the column, the plan and the command never disagree. The cap test is exact: normalizeGainDb hands the wanted gain back untouched when the ceiling does not bite. The bulk apply now counts such slots as untouched; the guide says so. normalize_plan_tests covers the 0.049 dB case and both sides of the step, and runs the plan and the command three more times on the take the command has just capped: nothing to do, nothing applied, nothing archived, the bytes as they were.
…ings requireNormalizeTarget and the Settings field both asked whether a target was outside the window as two comparisons, and NaN fails every comparison: a target of NaN passed both, the plan offered "this cuts nan dB" and the command filled the take with NaN samples. JUCE reads "nan" and "inf" out of typed text, so the field could store one (review of #142, P1). Both now ask whether the target is inside, in the positive. The field's rule moves out of the dialog into TargetLufs.h, JUCE-core only, with its window, so it can be tested without a window. commands_tests, normalize_plan_tests and the new normalize_step_tests run NaN and both infinities through the command, the plan and the typed text; the take is untouched by every one of them.
… ask a stuck store once Three gaps in the recall the first step makes (review of #142, P2): - A stored reading was trusted as it stood. Recall now takes only what the meter can say (plausibleReading): a finite, non-negative sample peak, and a loudness, when there is one, finite, above the -70 LUFS gate and beside a finite true peak. A reading the caller cannot use - `serves`, for Normalize whether a plan can be made of it - is also measured over, once, and the fresh reading replaces the row. The store now refuses an infinite loudness and a loudness beside a -inf true peak on the way in. - A store that could not be asked was asked again to file, so a locked one held the worker through a second busy timeout. The filing is skipped when the ask failed. - `failure` kept the ask's error while the filing then succeeded, so the log said "not kept" about a kept reading, and lost it when both failed. It now says why the history does not hold the reading, and only then. The recorder suite plants each implausible row past the store's checks and gets the take measured and the row replaced; puts a plausible row to a caller that refuses it and sees one question and a fresh reading; and, with an SQLite authorizer that refuses the ask but lets a filing through, shows no filing is tried. The store suite covers the new refusals.
…refuse in one voice The gap between the step and the click was open (review of #142, P2): the menu checked only pedalBusy, which background jobs do not set, nothing stopped a second request for the same slot, and recorded() opened the operation before the job knew anything - a take replaced between the window and the click was normalized, and left a row. normalizestep (app/NormalizeStep.h) now holds the protocol: - run is the first step - target first, then the take's reading, the history's for exactly these bytes or a fresh one, and its plan. - guardedBefore puts a check ahead of the job's history step: the slot must still hold the bytes the step hashed. Otherwise the job fails with "slot N changed since it was measured - Normalize again", before the operation opens: no row, no archive copy, no write. - StepsInFlight allows one first step per slot. A second request is dropped, and the menu item is greyed while the slot's reading is pending, as the player's button already was. The first step is named "Normalize slot N" and turns whatever it cannot make a plan of - no take, bytes that are not the pedal's, a card that will not be read - into the same banner as the plan's refusals, so every refusal of the action reads "Normalize slot N: ...". A capped boost's window is titled "toward" the target, since it stops short of it. normalize_step_tests runs the step on synthetic takes with no operation opened, the guard through a job built as the app builds it (unchanged bytes write and add one row; another sound under the same name, size and date, or an emptied slot, are refused with no row and no archive copy), and the one-step-per-slot rule.
Stopping the check put every slot of the selection back to the dash, including one a Normalize first step was still reading, whose "..." then vanished while its answer was on the way (review of #142, P2). The check now keeps the slots whose read has not landed and clears only those on a stop, and never a cell a Normalize step is reading.
…s it The meter reports a take with a +inf sample as a +inf sample peak beside a count of impossible samples, and the store keeps that reading; recall rejected every non-finite sample peak, so such a take was measured and filed again on every click (re-review of #142: 12 of 340 meter readings). plausibleReading now takes an infinite sample peak, or an infinite true peak beside a loudness, when the impossible-sample count is above zero, and still rejects either without it; NaN is rejected everywhere. The recorder suite covers both sides on their own, recalls a planted damaged row, and runs a real take with a +inf sample through: measured and filed once, recalled after.
…levels audio normalizeGainDb took a NaN or infinite measured loudness, target or ceiling and returned a gain that is not one. Normalize-on-upload passes the stored target straight in, and a settings file from a build whose field let "nan" through still reads it back, so an upload would bake a NaN gain into the take (re-review of #142). The gain rule now refuses such figures with a typed error; it is the one place the on-card command, its plan and the upload all pass through. Which targets are sensible stays the command's and the Settings field's business. loudness_tests runs NaN and both infinities as target, measured value and ceiling, for a boost and a cut, and pins ordinary calls as they were. The comment on kSmallestGainDb now says what was measured: the gain left to a capped take was 2.9e-07 dB, under a microdecibel, not hundredths.
…nothing without one Every read of the target took the settings file's number as it stood. A file written by a build whose field let "nan" through, or edited by hand to "inf", handed that on, and an infinite target now reaches normalizeGainDb's refusal on the message thread, from the player's own reading (#142 review). currentTargetLufs, and every other read of the target outside the upload path, now reads the stored text through targetlufs::parse. A text that is not a target is no target - none is put in its place - and it is reported once per launch: "The normalize target in Settings is not a number LooperCat can use (<value>): set it again in Settings -> Import". While it stays so, readings carry no verdict (the number and the peak, no attention colour), Normalize on a slot or a selection is refused with that sentence before anything is measured, its menu items lose the target from their names, and the Settings field starts empty with no ReplayGain equivalent until a target is typed; an empty field writes nothing over the stored text. With nothing stored yet the target is the one out of the box, as before. describeReading moves out of the window into LoudnessReport.h (JUCE-core only) and takes an optional target. normalize_step_tests covers the stored texts, the sentence, and readings with no target against the same readings with one.
…eat/normalize-measure-first
MSVC's app lane failed on normalize_step_tests: the suite reaches windows.h through JUCE, and windows.h still defines the 16-bit pointer qualifier near as an empty macro, so 'bool near(double, ...)' reached the compiler as 'bool (double, ...)'. The plan suite carries the same helper and escaped only because nothing there includes windows.h yet.
Member
Author
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #142. Stacked on #157 (
feat/loudness-in-history, the readings store); the base moves on as the stack below it is merged.Normalize on a single slot opened a warning window, ran the job, and then said "already at -14.0 LUFS, file untouched" — and left a history row explaining why it wrote nothing.
app/NormalizeStep.h,app/history/SlotLoudness.h): on the worker, as a background job — no history row, no archive, no journal. It reads and hashes the take, asks the history for a reading of exactly those bytes (Loudness readings belong to the bytes: keep them in the history, keyed by content hash #140), measures only when there is none or the stored one is implausible, and files the fresh one. The store is never opened just to ask.app/NormalizePlan.h): a pure unit over the reading and the target that agrees withcommands::normalizeon every input (outcome and words; a 400-take random differential shows no disagreement). Nothing to do: a toast in the words the LUFS column already uses, and nothing else. A refusal (damaged or unmeasurable take): the banner, "Normalize slot N: …". Something to do: the window opens with the numbers — "Measured -22.7 LUFS, this adds +4.7 dB", or "+2.1 dB possible: the -1 dBTP ceiling stops the rest", titled "toward" the target when capped.loudness::kSmallestGainDb. Before, a take capped at the ceiling offered "+0.0 dB" and rewrote identical bytes on every click. This changes the command, so the bulk apply now counts such slots as untouched (the guide says so).normalizeGainDb(the one place every path levels audio); a stored target that is not usable is reported once and readings are shown without a verdict until it is set again.Tests:
normalize_plan_tests(tolerance and ceiling boundaries, the capped take re-run three times, NaN/±inf),normalize_step_tests(new: target parse, the step, the hash guard as the app builds the job, the double request, verdict-less readings),history_recorder_tests(recall vs measure, planted implausible rows, a failed ask),history_store_tests,commands_tests,loudness_tests. Full ctest 67/67 locally, the six GUI harnesses included.