<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Changbin</title>
    <description>The latest articles on DEV Community by Changbin (@changbin2).</description>
    <link>https://dev.to/changbin2</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4239461%2Fd8b98d35-94d7-4f09-9b1a-4e591496c0e3.png</url>
      <title>DEV Community: Changbin</title>
      <link>https://dev.to/changbin2</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/changbin2"/>
    <language>en</language>
    <item>
      <title>Four times the meter lied, and each time it was me</title>
      <dc:creator>Changbin</dc:creator>
      <pubDate>Wed, 11 Nov 2026 16:19:43 +0000</pubDate>
      <link>https://dev.to/changbin2/four-times-the-meter-lied-and-each-time-it-was-me-1lna</link>
      <guid>https://dev.to/changbin2/four-times-the-meter-lied-and-each-time-it-was-me-1lna</guid>
      <description>&lt;p&gt;I work in audio, mostly on other people's masters. Four times I trusted an instrument and it told me something the file didn't say. Each time the error was mine, and each time the fix was one line of measurement.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. A container claimed 3:11. The audio ended at 2:52.8
&lt;/h2&gt;

&lt;p&gt;A render reported a duration the audio never reached. The player's progress bar trusted the header, so the bar kept moving through silence that was not part of the piece. Nobody notices this from the player. It is obvious from the stream.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# what the header claims&lt;/span&gt;
ffprobe -v error -show_entries &lt;span class="nv"&gt;format&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;duration -of &lt;span class="nv"&gt;csv&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;p&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0 track.mp3

&lt;span class="c"&gt;# what the audio actually contains: decode it and count samples&lt;/span&gt;
ffmpeg -v error -i track.mp3 -f s16le -ac 1 -ar 44100 - \
  &lt;span class="o"&gt;|&lt;/span&gt; wc -c &lt;span class="o"&gt;|&lt;/span&gt; awk &lt;span class="s1"&gt;'{print $1/2/44100" seconds"}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If those two numbers disagree, the header is the liar and the decoded stream is the fact. A file with no proper header writes whatever it wants into the label.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. &lt;code&gt;-ac 1&lt;/code&gt; on a stereo master is a sum, not a downmix
&lt;/h2&gt;

&lt;p&gt;Converting stereo to mono with &lt;code&gt;-ac 1&lt;/code&gt; adds the channels. On a master that was already near full scale, the mono version measured &lt;strong&gt;+1.92 dBFS&lt;/strong&gt; true peak on a file whose stereo version truly peaks at &lt;strong&gt;-1.07 dBFS&lt;/strong&gt;. That is not a peak in the recording. It is a peak the conversion created.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# the wrong way, for a level check&lt;/span&gt;
ffmpeg -i master.wav -ac 1 -af &lt;span class="nv"&gt;ebur128&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;peak&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt; -f null -

&lt;span class="c"&gt;# the way that keeps the level honest: average, do not add&lt;/span&gt;
ffmpeg -i master.wav -af &lt;span class="s2"&gt;"pan=mono|c0=0.5*c0+0.5*c1,ebur128=peak=true"&lt;/span&gt; -f null -
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;ebur128&lt;/code&gt; reports true peak and integrated loudness; read the summary block it prints at the end. Run the downmix through &lt;code&gt;pan&lt;/code&gt; first, then measure. Any mono number produced by &lt;code&gt;-ac 1&lt;/code&gt; on a stereo master is 6 dB optimistic, and it is not the file's level.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. A window mean hid a pulse
&lt;/h2&gt;

&lt;p&gt;Eight 4-bar windows on a mix read &lt;strong&gt;5.1 LU&lt;/strong&gt; of spread. It looked like one even shelf. At 100 ms frames the same file showed &lt;strong&gt;44 sub-bass hits on every other bar&lt;/strong&gt;, with adjacent bars swinging &lt;strong&gt;7-12 LU&lt;/strong&gt;, and &lt;strong&gt;104 of 1441 frames within 1 dB of peak&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Same file. Two stories. The window average was doing exactly what an average does: adding things that occur at different times and reporting the sum as if it were a shape.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="n"&gt;numpy&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;subprocess&lt;/span&gt;

&lt;span class="no"&gt;SAMPLE_RATE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;44100&lt;/span&gt;
&lt;span class="n"&gt;raw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"ffmpeg"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;"-v"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;"error"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;"-i"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;"mix.wav"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;"-f"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;"f32le"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;"-ac"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;"1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;"-ar"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="kt"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;SAMPLE_RATE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;"-"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;capture_output&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stdout&lt;/span&gt;
&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;frombuffer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dtype&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;float32&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;frame&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.1&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="no"&gt;SAMPLE_RATE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;          &lt;span class="c1"&gt;# 100 ms&lt;/span&gt;
&lt;span class="n"&gt;frames&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="n"&gt;frame&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;frame&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reshape&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;frame&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;rms&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sqrt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;frames&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mean&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;axis&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;db&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log10&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;maximum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rms&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;1e-12&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"frames:"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"spread:"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"dB"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"within 1 dB of peak:"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A window mean is a sum. A pulse is a distribution. If the question is "how loud is this section", a window is fine. If the question is "is this steady or is it hitting", only the frame count and the peak count answer it.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. The clip I flagged was a window's mistake
&lt;/h2&gt;

&lt;p&gt;I flagged a clipped hook opening by measuring per-line windows. The peaks were phrase onsets: the consonant attack at the front of each line, not distortion. Restricting the measurement to speech-active frames cleared the flag by &lt;strong&gt;+7 to +16 dB&lt;/strong&gt; off the ceiling.&lt;/p&gt;

&lt;p&gt;The lesson is not "measure less". It is: state what the metric was computed over, and check whether silence and onsets are inside that set. A peak measurement over a gated, speech-active region and a peak measurement over the whole file are two different instruments.&lt;/p&gt;

&lt;h2&gt;
  
  
  The rule I came out with
&lt;/h2&gt;

&lt;p&gt;Cases 3 and 4 came out of a trade with another artist. I gave him the window. He came back with the frames, because his test on a different track read 5.1 LU across eight windows and the file was a pulse, not a shelf. He wrote the closing sentence and I quote it because it is shorter than mine.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A level can't see placement. A placement can't see level.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Which means: before you accuse a file, check what your instrument can even see. Numbers you read off a measurement are worth exactly what the measurement's resolution is worth, and the ears still own the verdict.&lt;/p&gt;

</description>
      <category>ffmpeg</category>
      <category>audio</category>
      <category>video</category>
      <category>testing</category>
    </item>
  </channel>
</rss>
