Optagelse af søvnlyde

Apps til optagelse af søvnlyd er for nylig blevet meget populære. Jeg var også nysgerrig efter at høre, hvad jeg siger, når jeg sover, og om jeg snorker.

Forudsat at din telefon kan optage lyd, og du har en almindelig computer til at køre et simpelt Python-script, behøver du ikke købe andet eller betale for et abonnement for at tjekke de højlydte øjeblikke i din nattesøvn.

Først skal vi optage lyd, mens vi sover. I mit tilfælde startede jeg bare optagelsen på min iPhone, inden jeg gik i seng, og næste morgen havde jeg en otte timer lang M4A-fil.

At scrolle gennem så lang en optagelse er ikke praktisk, så lad os kun udtrække de fragmenter, der er højere end en given tærskel.

Lad os holde opsætningen af Python-miljøet uden for denne artikels omfang. Vi skal dog installere pydub (et Python-modul til at "manipulate audio with a simple and easy high-level interface") og oprette et tomt Python-script (f.eks. sleep.py) for at begynde at kode.

Det simpleste eksempel ser sådan ud:

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")

Her indlæser vi vores lydfil og opdeler den i stille sektioner med en given tærskel i dBFS (0 er den maksimale lydstyrke, så -54 betyder 54 decibel lavere end maksimum). I den sidste linje eksporterer vi resultatet til en mp3-fil.

Lad os nu gøre behandlingen hurtigere og finjustere et par parametre for at forbedre resultatet:

...

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 - (i ms) Bevar noget stilhed i begyndelsen og slutningen af fragmenterne. Dette forhindrer, at lyden lyder brat afskåret.
  • min_silence_len=10_000 - (i ms) minimumlængde af stilhed, der skal bruges til en opdeling.
  • silence_thresh=-54 - (i dBFS) alt lavere end dette betragtes som stilhed.
  • seek_step=1_000 - er trinstørrelsen for iteration gennem segmentet i millisekunder. Som standard itererer den hvert millisekund, hvilket gør behandlingen af en 8-timers lydfil meget lang.

For at skelne ét fragment fra et andet kan vi tilføje et "bip" imellem, forudgået og efterfulgt af et par sekunders stilhed. Vi kan også hæve lydstyrken på lydFragmenterne ved at tilføje antallet af decibel sådan: 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")

Den endelige version af mit script ser sådan ud:

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])

Jeg kan køre det som python sleep.py myaudio.m4a, og det genererer en kortere version af min søvnlydoptagelse. Det fylder normalt 8-12 minutter ud af otte timers søvn.