Manipuler du son (♣)

Dans cette feuille, vous allez apprendre à effectuer quelques manipulations et traitements simples sur du son. Elle s’adresse aux étudiants désirant s’éloigner des images et de traîter d’autres types de données. Les étapes de traîtement VI-ME-RÉ-BAR restent les mêmes mais l’import et l’extraction des attributs peut changer significativement. Si vous avez déjà des difficultés sur ce cours, il serait préférable de rester avec des données d’images.

import glob
import io
import os
from scipy.io import wavfile
import numpy as np
import IPython
import matplotlib.pyplot as plt
import pandas as pd
from PIL import Image, ImageTk

%load_ext autoreload
%autoreload 2

from intro_science_donnees import data, image_grid
from utilities import *

À titre d’illustration, nous allons étudier un jeu de données contenant des enregistrements de sons de battements de coeur normaux ou anormaux, que nous essaierons de classifier:

dataset_dir = os.path.join(data.dir, "BattementsCoeur/")
!ls {dataset_dir}

Utilisons la bibliothèque scipy.io pour lire un fichier audio, que nous affectons à une variable son pour pouvoir la manipuler par la suite:

file = os.path.join(dataset_dir, "normal_1.wav")
rate, audio = wavfile.read(file)

Un son se présente comme un tableau de valeurs entières correspondant à l’onde de pression acoustique enregistrée par un microphone. Lors du passage en données numériques, ce signal (originellement continu) est échantillonné c’est à dire découpées en une série de valeurs prélevés à interval régulier. Ce sont ces valeurs discrétisés que l’on peut stocker dans un ordinateur.

La fréquence d’échantillonnage nous donne le nombre d’échantillons par seconde. Générallement, la musique est échantillonnée à 44100 Hz pour couvrir toute la gamme de fréquence de l’audition humaine. Ici, on est à 2000 Hz donc toutes les hautes fréquences (les aigües) sont filtrés.

Attention, les sons peuvent être stéréos ou mono. Un son stéréo a été enregistré par deux microphones pour restituer l’audition humaine (deux oreilles). Par conséquent, un son stéréo contient deux suites temporelles de nombres pour chaque canal d’enregistrement.

Exercice : Quel est la fréquence d’échantillonnage du son de coeur importé? Votre son est-il enregistré en stéréo ou mono?

Indication : Consulter la documentation de wavfile.read

# VOTRE CODE ICI
raise NotImplementedError()

VOTRE RÉPONSE ICI

On peut écouter un fichier audio dans un feuille Jupyter avec la commande suivante. Attention, cela peut ne pas marcher avec le navigateur Chrome mais devrait fonctionner avec Firefox.

IPython.display.Audio(file)

Exercice : Afficher l’onde de pression en fonction du temps (forme d’onde) avec la fonction plot de la librairie matplotlib.

# VOTRE CODE ICI
raise NotImplementedError()

Exercice : Combien de secondes dure le fichier?

VOTRE RÉPONSE ICI

Nous allons maintenant ouvrir un ensemble de sons et stocker leurs valeurs dans une table :

# VOTRE CODE ICI
raise NotImplementedError()

Domaine temporel ou fréquentiel

Pour travailler avec des sons, il nous faut calculer des descripteurs.

On peut interpréter un son à la fois dans le domaine fréquentiel ou temporel:

  • Le domaine temporel s’intéresse à la forme de l’onde tel que nous l’avons affichée plus haut.

  • Le domaine fréquentiel cherche à représenter le son en fréquences à l’aide d’une transformation de Fourier

Avec une transformation de Fourier on peut représenter le son sous la forme d’une «image», un spectrogramme. Voyons comment obtenir un spectrogramme sur la première image de notre jeu de données.

audio = df.iloc[0]["audio"]
spectrogram =  plt.specgram(audio, Fs=rate)

L’axe des x représente le temps tandis que l’axe des y représente les fréquences. Les hautes valeurs de y sont les composantes aigües du son tandis que les basses valeurs valeurs de fréquences correspondent au composantes graves du son. Le son est formée d’une superposition de fréquences qui peuvent être :

  • Localisés sur des lignes horizontales. On parle de sons harmoniques ou purs. Ce serait le cas de chants d’oiseaux ou d’une flute par exemple. On peut clairement distingué une note jouée.

  • Étalé dans la gamme de fréquence, se traduisant par des lignes hotizontales. Ce sont des sons bruités, percussifs tels qu’une caisse claire de batterie ou une porte qui claque.

Exercice: Nos battements de coeurs sont-ils plutôt des sons harmoniques ou bruités?

On récupère les valeurs du spectrogramme dans un tableau numpy.array comme ceci:

freqs_temps = spectrogram[0] # Le spectrogramme
freqs = spectrogram[1] # L'axe des fréquences
temps = spectrogram[2] # L'axe temporel
fig =  spectrogram[3] # La figure affichée ce-dessus

Attributs

Vous pouvez maintenant réfléchir à des attributs dans le domaine temporel (forme d’onde 1D), tempo-fréquentiel (spectrogramme 2D) ou purement fréquentiel (moyenne des différentes fréquences dans le temps 1D). Pensez qu’un spectrogramme peut-être vu comme une image sur laquelle vous maîtriser déjà des traitements et de l’extraction d’attributs.

Exercice :

  • Quelles est la taille de notre spectrogramme?

  • Combien de bandes temporelles possède notre spectrogramme (nombre de pas selon l’axe temporel)?

  • Combien de bandes fréquentielle possède notre spectrogramme (nombre de pas selon l’axe des fréquences)?

# VOTRE CODE ICI
raise NotImplementedError()

VOTRE RÉPONSE ICI

Voici comment afficher les spectrogrammes de tous nos sons avec image_grid:

def spectrogram_PIL(audio):
    """Renvoie le spectrogramme du son sous forme d'une image PIL"""
    # Compute the spectrogram
    spectrogram = plt.specgram(audio, Fs=rate)[0];
    # Convert into an array of ints between 0 and 255
    spectrogram_uint = (spectrogram * 255 / np.mean(freqs_temps)).astype(np.uint8)
    # Convert into a PIL image
    return Image.fromarray(spectrogram_uint, mode='L')
# Une table contenant le le spectrogramme de chaque son du jeu de données
spectrograms_PIL = df['audio'].map(spectrogram_PIL)
image_grid(spectrograms_PIL, titles=spectrograms_PIL.index)

Dans le domaine fréquentiel, des attributs audio fréquents sont le centroïde spectral et l’étalement de spectre.

Vous trouverez des idées d’attributs audio dans la publication suivante.

Conclusion

Vous avons vu dans cette feuille comment charger un son dans Python et effectuer quelques manipulations, visualisations et passer dans le domaine fréquentiel. Cela a été l’occasion de mieux comprendre la décomposition d’un son en fréquence. Faites part de votre intérêt de choisir un projet avec du son à votre chargé de TD avant de vous lancer.