# Podcasts with loud background music under the speech

Source: https://easeweb.dev/learn/background-audio
Topics: Images and media > Recordings keep speech clear over background sound (WCAG 1.4.7)
The fix: Either mix the music at least 20 dB below the voice, measured on the result, or leave it out; or publish a version with no music and offer it from a button beside the player that keeps the place.
Test with: audio measurement, keyboard, screen reader
References: https://www.w3.org/WAI/WCAG22/Understanding/low-or-no-background-audio.html https://www.w3.org/WAI/WCAG22/Techniques/general/G56 https://manual.audacityteam.org/man/contrast.html https://ffmpeg.org/ffmpeg-filters.html#amix https://ffmpeg.org/ffmpeg-filters.html#loudnorm https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/audio

This is a recreated teaching example, not a finding about a named client. The code is a starting pattern: test it in your own product. No completed assistive-technology test is claimed here.

## What happens

A coffee roaster's podcast, Small Batch, puts each episode on its website with a transcript under the player. Episode 12 is about storing beans. The host, Sam, says to keep them in an airtight tin somewhere cool and dark, to buy only what you'll use in two weeks, and never to keep them in the fridge. The producer wanted the show to sound warm, so the theme tune plays under the whole episode, not only the opening. They mix it with one FFmpeg command: both tracks levelled to the same loudness, and the music lowered by 8 dB. Measured on the result, the music sits only about 6 dB below the voice, under every word. Someone with normal hearing follows it with a little effort. A listener who is hard of hearing hears a voice and a tune of almost the same weight, and can't pick out whether the fridge is a yes or a no. The page has no way to turn the music off.

## Why it fails

Success criterion 1.4.7, at Level AAA, covers prerecorded audio-only content that is mostly speech in the foreground, such as a podcast or an audio guide, and is not an audio CAPTCHA, an audio logo or singing. It asks for one of three things: no background sound; a way to turn the background sound off; or background sound at least 20 dB lower than the speech, apart from occasional sounds of a second or two. WCAG notes that 20 dB lower is about four times quieter, and W3C chose the figure from research on assistive listening systems. Here the music is under the speech throughout, about 6 dB down, and can't be turned off, so it fails all three.

The mix setting is not the gap. Levelling by loudness, as `loudnorm` does, weighs frequencies the way the ear hears them, while the gap is measured on the sound itself. So lowering the music by 8 dB after levelling left it about 6 dB below the voice, and the producer, reading their own command, believed it was further down than it was. The transcript under the player passes 1.2.1 for people who can't hear the episode, but it doesn't help someone who listens and needs the speech clear.

## Who is affected

Anyone listening through a phone speaker, or in a car or a kitchen, has other sound around the episode as well, and the music under the speech makes it harder still. A clear mix helps them too.

## What automation and AI miss

No rule in axe looks at the sound inside a file. EaseWeb's audit raises a review lead for every recorded `<audio>` player, under 1.2.1, asking for a transcript; it does not measure the mix. Measuring needs a person to choose the parts: Audacity's Contrast tool compares a selection of speech with a selection of background sound alone, by RMS, and says whether the difference passes 1.4.7. Where the music never stops, the background-only parts are the gaps between lines and the intro, so the producer's separate tracks give the surest numbers. Tools that use AI to split a finished file into voice and music can give an estimate when the tracks are lost.

## Before: music lowered by too little, under the whole episode

```html
<h2>Small Batch, episode 12: storing beans</h2>
<audio controls preload="metadata" src="small-batch-12.mp3"></audio>
<details>
  <summary>Transcript</summary>
  <p>[Theme music plays under the whole episode]</p>
  <p><b>Sam:</b> Welcome to Small Batch. This week: how to store your coffee beans.</p>
  <p><b>Sam:</b> Keep them in an airtight tin, somewhere cool and dark.
    Buy only what you’ll use in two weeks.</p>
  <p><b>Sam:</b> And never keep them in the fridge.
    They take in damp, and the smell of everything around them.</p>
</details>
```

```sh mix.sh
# Level the voice and the theme to the same loudness, lower the theme,
# and lay it under the voice for the whole episode.
ffmpeg -i voice.wav -i theme.wav -filter_complex \
  "[0]loudnorm=I=-18[voice];[1]loudnorm=I=-18,volume=-8dB[music];[voice][music]amix=inputs=2:duration=first:normalize=0" \
  small-batch-12.mp3
```

- The theme plays under every word, not only between the lines.
- Lowered by 8 dB after levelling, it measures only about 6 dB below the voice.
- There is no version without it, and no control on the page to turn it off.

## After: the music mixed at least 20 dB below the voice

```html
<h2>Small Batch, episode 12: storing beans</h2>
<audio controls preload="metadata" src="small-batch-12.mp3"></audio>
<details>
  <summary>Transcript</summary>
  <p>[Theme music plays under the whole episode]</p>
  <p><b>Sam:</b> Welcome to Small Batch. This week: how to store your coffee beans.</p>
  <p><b>Sam:</b> Keep them in an airtight tin, somewhere cool and dark.
    Buy only what you’ll use in two weeks.</p>
  <p><b>Sam:</b> And never keep them in the fridge.
    They take in damp, and the smell of everything around them.</p>
</details>
```

```sh mix.sh
# Level the voice and the theme to the same loudness, lower the theme,
# and lay it under the voice for the whole episode.
# Measured in Audacity's Contrast tool, the theme sits about 23 dB below the voice.
ffmpeg -i voice.wav -i theme.wav -filter_complex \
  "[0]loudnorm=I=-18[voice];[1]loudnorm=I=-18,volume=-25dB[music];[voice][music]amix=inputs=2:duration=first:normalize=0" \
  small-batch-12.mp3
```

1. Lower the music further in the mix: here by 25 dB after levelling.
2. Measure the result, voice against music alone, in several places through the episode. Here it comes out about 23 dB, about 2 dB short of the setting, and over the 20 dB the criterion asks for.
3. Write the measured gap beside the command, so the next episode is mixed and checked the same way.

This fixes the Before and passes 1.4.7. The music is still there, but about five times quieter than the voice instead of about one and a half, and every listener gets the same clear file, on the website and in every podcast app. Leaving the music out entirely passes too.

## After: a version with no music, and a button to switch to it

```html
<h2>Small Batch, episode 12: storing beans</h2>
<audio id="episode" controls preload="metadata"
  src="small-batch-12.mp3" data-voice="small-batch-12-voice.mp3"></audio>
<button type="button" id="music" aria-pressed="true">Background music</button>
<details>
  <summary>Transcript</summary>
  <p>[Theme music plays under the whole episode]</p>
  <p><b>Sam:</b> Welcome to Small Batch. This week: how to store your coffee beans.</p>
  <p><b>Sam:</b> Keep them in an airtight tin, somewhere cool and dark.
    Buy only what you’ll use in two weeks.</p>
  <p><b>Sam:</b> And never keep them in the fridge.
    They take in damp, and the smell of everything around them.</p>
</details>

<script>
  const episode = document.getElementById('episode');
  const withMusic = episode.getAttribute('src');
  document.getElementById('music').addEventListener('click', (event) => {
    const button = event.currentTarget;
    const on = button.getAttribute('aria-pressed') !== 'true';
    const { currentTime, paused, playbackRate } = episode;
    episode.src = on ? withMusic : episode.dataset.voice;
    // Carry on from the same moment, at the same speed.
    episode.addEventListener('loadedmetadata', () => {
      episode.currentTime = currentTime;
      episode.playbackRate = playbackRate;
      if (!paused) episode.play();
    }, { once: true });
    button.setAttribute('aria-pressed', String(on));
  });
</script>
```

```sh mix.sh
# Level the voice and the theme to the same loudness, lower the theme,
# and lay it under the voice for the whole episode.
ffmpeg -i voice.wav -i theme.wav -filter_complex \
  "[0]loudnorm=I=-18[voice];[1]loudnorm=I=-18,volume=-8dB[music];[voice][music]amix=inputs=2:duration=first:normalize=0" \
  small-batch-12.mp3
# The same voice, levelled the same way, with no music.
ffmpeg -i voice.wav -af loudnorm=I=-18 small-batch-12-voice.mp3
```

1. Export a second file from the same voice track, with no music. It is the same length, so a moment in one is the same moment in the other.
2. Name it on the player, in `data-voice`.
3. Put a Background music button right after the player, with `aria-pressed="true"`. It says what it controls and whether it is on.
4. On a click, swap the file, then restore the place and the speed once it has loaded, so turning the music off halfway doesn't start the episode again.

This fixes the Before and passes 1.4.7, because the background sound can be turned off. It keeps the mix the producer wanted for everyone else. But every listener starts with the loud mix and has to find the button, and the episode in podcast apps, from the same feed, keeps the music with no way to turn it off.

## Which option to use

Both pass 1.4.7. Mix the music at least 20 dB down: it does the same job with one file and nothing to find, and the clear mix goes wherever the episode goes, including podcast apps. The button keeps the louder mix for listeners who like it, at the cost of a second file, a script, and a listener who has to know to look for it. If the music matters to the show, use the button as well as a quieter mix, not instead of one.

## Implementation decisions

Lower the music only where someone speaks, if the show needs its theme at full level. Editors call this ducking: the intro and the breaks between segments play the theme as it is, and it drops under each line. An intro, a sting between segments or a jingle where nobody speaks is not background to speech. Under speech, the criterion allows a louder sound only if it is occasional and lasts a second or two, such as a door or a laugh.

Measure the gap on the result, the way the criterion is tested. Audacity's Contrast tool, under Analyze, takes a selection of speech as the foreground and a selection of background sound alone as the background, measures each by RMS, and reports a pass when the difference is 20 dB or more. Its manual says to check several places, which matters here, since music changes through an episode. Settings don't tell you the gap: levelling by loudness, compression and the music's own dynamics all move it, which is why the 8 dB setting here measured about 6.

The criterion covers audio-only content: podcasts, audio guides, audio clips. A video's soundtrack is not covered by 1.4.7, though the same mix helps its viewers too. It doesn't cover audio CAPTCHAs, audio logos such as a station's three-note jingle, or singing and rapping, where the music is the point.

The same goes for sound recorded with the speech, such as an interview in a busy café. It can't be lowered after the fact as easily as a separate music track. Record somewhere quieter, with the microphone close to each speaker, and use noise reduction carefully, since too much makes the voice harder to follow. Then measure the voice against the room's sound alone, in a pause.

A transcript is a separate requirement, 1.2.1, for people who can't hear the episode at all, and it's covered in [podcasts and silent videos with no transcript](/learn/transcripts). Keep it whichever fix you choose.

## Verify the fix

1. Open the published file, or the separate tracks, in Audacity. With Analyze, Contrast, select a stretch of speech as the foreground and a stretch of music alone, such as the intro or a pause between lines, as the background. The difference is 20 dB or more.
2. Measure again in several places: near the start, the middle and the end, and wherever the music changes. Each passes.
3. Listen to the whole episode. The music never rises under speech, and any louder sound under a line lasts no more than a second or two.
4. Where there is a switch, use only the keyboard, then a screen reader: find the Background music button beside the player, hear whether it is pressed, and turn it off. The episode carries on from the same moment, with no music.

Record the OS, browser and assistive-technology versions, the build, the date, and the actual result of each step, with the measuring tool and its version. Repeat for each new episode, since the music and the mix change with it.

## Limits

This is a starting pattern, not a guarantee for every application. A measured gap of 20 dB is the criterion's line, not a promise that every listener will follow the speech: the people who will listen are the best test. Sound that starts by itself is covered in [autoplaying sound with no way to stop it](/learn/autoplay-audio), and the transcript in [podcasts and silent videos with no transcript](/learn/transcripts). Test the complete journey on the players and podcast apps the episode really plays in.

## What screen readers do (tested by hand by EaseWeb)

`aria-pressed`: Makes a button a toggle that is on or off.

| Screen reader | Browser and system | Press it | Tab to it |
| --- | --- | --- | --- |
| JAWS | Chrome, Windows 11 | Not tested | Not tested |
| NVDA | Chrome, Windows 11 | Not tested | Not tested |
| JAWS | Edge, Windows 11 | Not tested | Not tested |
| NVDA | Firefox, Windows 11 | Not tested | Not tested |
| NVDA | Edge, Windows 11 | Not tested | Not tested |
| JAWS | Firefox, Windows 11 | Not tested | Not tested |
| VoiceOver | Safari, macOS | Not tested | Not tested |
| VoiceOver | Safari, iOS | Not tested | Not tested |
| TalkBack | Chrome, Android | Not tested | Not tested |

https://easeweb.dev/learn/screen-reader-support

---

Get this guide in your coding agent: MCP https://easeweb.dev/mcp (setup at https://easeweb.dev/services/developers)
