<?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: Johnny Nguyen</title>
    <description>The latest articles on DEV Community by Johnny Nguyen (@johnny_nguyen).</description>
    <link>https://dev.to/johnny_nguyen</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%2F4242073%2F796acc71-4e6f-4afc-af64-e301e5b295ce.png</url>
      <title>DEV Community: Johnny Nguyen</title>
      <link>https://dev.to/johnny_nguyen</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/johnny_nguyen"/>
    <language>en</language>
    <item>
      <title>What it takes to turn an iPhone microphone into a decibel meter</title>
      <dc:creator>Johnny Nguyen</dc:creator>
      <pubDate>Fri, 13 Nov 2026 03:41:17 +0000</pubDate>
      <link>https://dev.to/wlab/what-it-takes-to-turn-an-iphone-microphone-into-a-decibel-meter-1j4p</link>
      <guid>https://dev.to/wlab/what-it-takes-to-turn-an-iphone-microphone-into-a-decibel-meter-1j4p</guid>
      <description>&lt;p&gt;I work on Noise Meter at W Lab. It is an iPhone app that shows the sound level in decibels and logs a whole night of it, so someone with a noisy neighbour can show when it was loud. This post is about the measuring part: what happens between the microphone and the number on screen, and the places where I had to be honest about what a phone can't do.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting clean samples
&lt;/h2&gt;

&lt;p&gt;iOS processes microphone input by default: automatic gain, noise suppression, EQ. All of that is exactly what a meter must not have, because a quiet room would be boosted and a loud one pulled down. The audio session mode that turns it off is &lt;code&gt;.measurement&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setCategory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;measurement&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setPreferredSampleRate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;48_000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From there it is an &lt;code&gt;AVAudioEngine&lt;/code&gt; input tap with a 4800-frame buffer, which is 100 ms at 48 kHz. I ask for 48 kHz but build the analyzer for whatever rate the hardware returns. I also select the built-in microphone's bottom data source with an omnidirectional pattern, and re-select it on every route change, because a route change can move the input.&lt;/p&gt;

&lt;p&gt;One Swift 6 trap is worth knowing about. A closure created inside a &lt;code&gt;@MainActor&lt;/code&gt; method inherits that isolation, and the audio thread calling it is a runtime trap. The tap block has to be built in a nonisolated context.&lt;/p&gt;

&lt;h2&gt;
  
  
  Weighting: A, C and Z
&lt;/h2&gt;

&lt;p&gt;A raw level over-reports low rumble compared with what people hear, so sound level meters apply a frequency weighting. A-weighting follows the ear, C-weighting keeps much more bass, and Z is flat.&lt;/p&gt;

&lt;p&gt;The standard, IEC 61672-1, defines these curves by their analog poles (20.6 Hz, 107.7 Hz, 737.9 Hz and 12194 Hz). I bilinear-transform those poles at runtime for the actual sample rate into up to three biquad sections, and normalise the gain to 0 dB at 1 kHz. A-weighting is a high-pass, a middle section and a low-pass; C drops the middle section; Z is a passthrough. The filter runs per sample in plain Swift &lt;code&gt;Double&lt;/code&gt;s, with no FFT.&lt;/p&gt;

&lt;p&gt;The bilinear transform warps the top of the spectrum, so I wrote down how far off it is: at 48 kHz it stays inside the standard's class 2 tolerance from 31.5 Hz to 8 kHz, and is 2.6 dB low at 12.5 kHz. The tests feed sines through the filters at 48 and 44.1 kHz and compare the result with the standard's design values.&lt;/p&gt;

&lt;h2&gt;
  
  
  From samples to a level
&lt;/h2&gt;

&lt;p&gt;Each weighted sample is squared. Everything after that is an average of those squares:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The number on screen is an exponential average with a time constant of 125 ms (Fast) or 1 s (Slow), the two responses a hardware meter offers.&lt;/li&gt;
&lt;li&gt;Every second, the plain mean of that second's squares becomes one stored row: the second's Leq, plus the highest and lowest Fast/Slow level inside it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Leq, the equivalent continuous level, is the number noise standards are written in. It is an average of energy, not of decibels, so the Leq of a recording is &lt;code&gt;10·log10&lt;/code&gt; of the mean of the per-second energies. A test pins this down: one second at 60 dB and one at 70 dB give 67.4 dB, not 65.&lt;/p&gt;

&lt;p&gt;That distinction matters for the app's main use. Footsteps upstairs are short and loud. They barely move a night's Leq, but they show up in the per-second maximum, which is why the app keeps both.&lt;/p&gt;

&lt;p&gt;The first half second after starting is thrown away. The filter needs to settle, and without that the start-up ramp would be recorded as the minimum of every session.&lt;/p&gt;

&lt;h2&gt;
  
  
  The hard part: dBFS is not dB
&lt;/h2&gt;

&lt;p&gt;Everything above produces dBFS, a level relative to the largest number the converter can represent. A full-scale sine reads −3.01. A sound level meter shows dB SPL, relative to 20 micropascals. The two differ by a constant that depends on the microphone and the hardware around it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;level&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="n"&gt;dBFS&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Double&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;Double&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;dBFS&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;  &lt;span class="c1"&gt;// total = model + user&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That constant is the whole accuracy story. The app ships with a default offset measured on one iPhone, in a table keyed by the model identifier from &lt;code&gt;uname&lt;/code&gt;, and I do not claim a margin of error for it. What it offers instead is calibration: put the phone next to a real sound level meter set to A-weighting, play a steady noise, type in the meter's reading, and the app stores the difference as a user offset in 0.1 dB steps. Every recording keeps the offset and calibration state it was measured with.&lt;/p&gt;

&lt;p&gt;Two quick checks come before that, for people without a meter: a quiet-room check (does the level drop when the room is quiet?) and a response check (does the reading move at least 15 dB when you turn the TV up and down?). They can't make the phone accurate. They catch a blocked or broken microphone, which is the more common failure.&lt;/p&gt;

&lt;p&gt;A calibration offset also can't fix physics. The microphone has a self-noise floor, so a very quiet room never reads as quiet as it is, and a loud enough sound clips the converter. No offset corrects either, and the app tells people its readings are a reference, not a certified measurement.&lt;/p&gt;

&lt;h2&gt;
  
  
  A whole night without recording audio
&lt;/h2&gt;

&lt;p&gt;The app never writes audio to disk. People put this next to their bed, and a level meter has no reason to keep what was said in the room. What is stored per second is three numbers, Leq, max and min, as 16-bit hundredths of a decibel. That is 6 bytes a second, so twelve hours is about 259 KB.&lt;/p&gt;

&lt;p&gt;Rows are flushed to storage every ten seconds, so a crash or a dead battery loses almost nothing, and a session left open by a kill is recovered at the next launch.&lt;/p&gt;

&lt;p&gt;Phone calls interrupt the audio session. The recording pauses, and the paused seconds are stored as empty slots, not skipped, so every later second keeps its true clock time. That matters when the point of the recording is to say "it was loud at 2:14".&lt;/p&gt;

&lt;h2&gt;
  
  
  Making the number mean something
&lt;/h2&gt;

&lt;p&gt;A bare "52 dB" tells most people nothing. Two things on screen give it meaning.&lt;/p&gt;

&lt;p&gt;Under every reading, the app names a place about as loud: a library, a subway car, a coffee shop. The fifteen examples and their levels come from a noise level guide published on the website of Japan's Ministry of the Environment, and the app picks the nearest one.&lt;/p&gt;

&lt;p&gt;The chart also draws Japan's environmental noise standards for the chosen area type, for example 55 dB by day and 45 dB at night in residential areas. Those standards are assessed outdoors over the whole day or night, so the app labels the comparison as a reference.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I would tell someone starting the same thing
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Use &lt;code&gt;.measurement&lt;/code&gt; mode and check which microphone you actually got.&lt;/li&gt;
&lt;li&gt;Implement the weighting from the standard's poles, then test it against the standard's table. It is less code than it sounds.&lt;/li&gt;
&lt;li&gt;Average energy, never decibels.&lt;/li&gt;
&lt;li&gt;Be plain about calibration. An uncalibrated phone gives a useful relative reading and an unknown absolute one, and users deserve to know which is which.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Noise Meter is on the App Store for iPhone as "Noise Meter - Decibel Level": &lt;a href="https://w-lab.app/apps/noise-meter/" rel="noopener noreferrer"&gt;https://w-lab.app/apps/noise-meter/&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ios</category>
      <category>swift</category>
      <category>audio</category>
      <category>showdev</category>
    </item>
  </channel>
</rss>
