Roman Klimenko
DA

Recording sleep sounds

Sleep audio recording apps have recently become very popular. I was also curious to hear what I say when I sleep and whether I snore.

If your phone can record audio and your computer can run a simple Python script, you don't need to buy anything or pay for a subscription to find the loud moments during the night.

First, we need to record audio while we sleep. In my case, I just started recording on my iPhone before I went to bed, and the next morning, I had an eight-hour M4A file.

Searching through such a long recording by hand is tedious, so let's extract only the fragments above a chosen volume threshold.

I'll leave the Python environment setup out of this article. We only need to install pydub, a high-level Python library for working with audio, and create an empty script such as sleep.py.

The simplest example looks like this:

from pydub import AudioSegment
from pydub.silence import split_on_silence

audio = AudioSegment.from_file("sleep_audio.m4a", format="m4a")

chunks = split_on_silence(audio, silence_thresh=-54)

sum(chunks).export("loud_fragments.mp3", format="mp3")

Here, we load the audio file and use a threshold in dBFS to separate louder fragments from silence. Zero is the maximum level, so -54 means 54 decibels below the maximum. The last line exports the result as an MP3 file.

Now, let's make the processing faster and fine-tune a few parameters to improve the outcome:

...

chunks = split_on_silence(
    audio,
    keep_silence=10_000,
    min_silence_len=10_000,
    silence_thresh=-54,
    seek_step=1_000)

...
  • keep_silence=10_000: keeps ten seconds of silence at the beginning and end of each chunk, so the sound isn't cut off abruptly.
  • min_silence_len=10_000: requires at least ten seconds of silence before splitting the audio.
  • silence_thresh=-54: treats anything quieter than -54 dBFS as silence.
  • seek_step=1_000: scans the audio in one-second steps. The default is one millisecond, which makes an eight-hour recording take much longer to process.

To distinguish one fragment from another, we can add a "beep" in between, preceded and followed by a few seconds of silence. We can also raise the volume of the audio chunks by adding the number of decibels like: chunk + 3.

...

from pydub.generators import Square

...

silence = AudioSegment.silent(duration=500)
beep = Square(1_000).to_audio_segment(duration=250, volume=-54)
beep_with_silence = silence + beep + silence

loud_fragments = sum([(chunk + 3) + beep_with_silence for chunk in chunks])

loud_fragments.export("loud_fragments.mp3", format="mp3")

The final version of my script looks like this:

import sys
from pathlib import Path

from pydub import AudioSegment
from pydub.generators import Square
from pydub.silence import split_on_silence

GAIN = 3
KEEP_SILENCE = 10_000
MIN_SILENCE_LEN = 60_000
SEEK_STEP = 100
SILENCE_THRESH = -54


def main(file_path):

    audio = AudioSegment.from_file(file_path, format="m4a")

    print(f"Loaded audio: {audio.duration_seconds} seconds")

    chunks = [chunk for chunk in split_on_silence(audio,
                                                  keep_silence=KEEP_SILENCE,
                                                  min_silence_len=MIN_SILENCE_LEN,
                                                  seek_step=SEEK_STEP,
                                                  silence_thresh=SILENCE_THRESH)
              if chunk.duration_seconds > (KEEP_SILENCE * 2 / 1_000)]

    print(f"Found {len(chunks)} chunks")

    silence = AudioSegment.silent(duration=500)
    beep = Square(1_000).to_audio_segment(duration=250, volume=SILENCE_THRESH)
    beep_with_silence = silence + beep + silence

    loud_audio = sum([(chunk + GAIN) + beep_with_silence for chunk in chunks])

    print(f"Combined loud audio: {loud_audio.duration_seconds} seconds")

    Path("./data").mkdir(parents=True, exist_ok=True)

    loud_audio.export(f"./data/{Path(file_path).stem}_loud.mp3", format="mp3")


if __name__ == "__main__":
    if len(sys.argv) != 2:
        print("Usage: python sleep.py <file_path>")
        sys.exit(1)
    main(sys.argv[1])

I run it with python sleep.py myaudio.m4a, and it creates a shorter version of the recording. The result is usually between 8 and 12 minutes long for eight hours of sleep.