Lors de mes présentations orales, j'ai diffusé une animation réalisée à l'aide du moteur Manim, dont l'excellente chaîne 3Blue1Brown se sert pour mettre en image des concepts mathématiques
La vidéo a été étoffée depuis la première présentation. C'est ici l'occasion d'expliquer comment générer des animations dans ce genre.
La méthode Hagerman et Olofsson offre la possibilité de séparer les sources de parole et de bruit en sortie d'aide auditive, afin d'en étudier les effets distincts. Il en est beaucoup question dans mes recherches. Elle nécessite deux enregistrements A et B (dont le bruit est en opposition de phase), comme l'expliquent les formules suivantes :
- l'addition des signaux A et B génère le double du signal, sans le bruit,
- tandis que la soustraction génère le double du bruit, sans le signal.
Installation de Manim dans un Notebook Jupyter
Jupyter est une application web de programmation interactive, héritée de iPython et dédiée aux langages interprétés tels que Julia, Python et R - d'où le nom. Quant à Manim il peut être utilisé de deux manières.
- soit en ligne de commande :
manim -p -ql example.py SquareToCircle
- soit dans une cellule Jupyter :
%%manim [CLI options] MyAwesomeScene
class MyAweseomeScene(Scene):
def construct(self):
...
Dans les deux cas, on obtiendra un fichier vidéo .mp4. Pour installer Manim sous Debian, il faudra commencer par ajouter les dépendances :
apt install build-essential python3-dev libcairo2-dev libpango1.0-dev ffmpeg
Puis utiliser le gestionnaire de paquets pip, ou conda (ou encore son alternative ultra rapide mamba) pour ajouter le programme en question :
conda install manim
Code source
Ci-dessous, le code saisi dans une cellule Jupyter pour générer la première partie de la vidéo. Dans cette "Scene", on utilise les fonctions suivantes :
Writepour faire apparaître le texte progressivementFadeOutpour le faire disparaître en fonduAxespour générer un repère orthogonalScalepour mettre l'objet à l'échelleShiftpour décaler l'objetCreatepour dessiner l'objet progressivementFlippour retourner l'objetStretchpour étirer l'objet
Enfin, la fonction sinus provient du paquet numpy. Idem pour la fonction random qui permet de générer du bruit aléatoire.
%%manim -ql HagermanOlofsson
class HagermanOlofsson(Scene):
def construct(self):
title1 = Text("La méthode", font="Ubuntu")
title2 = Text("Hagerman & Olofsson", font="Ubuntu").next_to(title1, DOWN)
self.play(Write(title1), Write(title2))
self.wait(2)
self.play(FadeOut(title1), FadeOut(title2))
title3 = Text("L'addition des signaux de parole avec le bruit en opposition de phase", font="Ubuntu").scale(0.6)
title4 = Text("donne le double du signal, sans le bruit...", font="Ubuntu").scale(0.6).next_to(title3, DOWN)
self.play(Write(title3), Write(title4))
self.wait(3)
self.play(FadeOut(title3), FadeOut(title4))
axes_up = Axes(
x_range=[0, 12.56, 1],
y_range= [-2, 2, 1],
#axes_color=GRAY,
)
axes_up.scale(0.5).shift(1.8 * UP)
axes_down = Axes(
x_range=[0, 12.56, 1],
y_range= [-2, 2, 1],
#axes_color=GRAY,
)
axes_down.scale(0.5).shift(2 * DOWN)
labels_up = axes_up.get_axis_labels(
Tex(r"\tiny{temps [s]}"), Tex(r"\tiny{niveau [dB]}")
)
labels_down = axes_down.get_axis_labels(
Tex(r"\tiny{temps [s]}"), Tex(r"\tiny{niveau [dB]}")
)
#Graph Up
self.play(Create(axes_up), run_time = 2)
self.play(Create(labels_up), run_time = 1)
graph_up = axes_up.plot(lambda x : np.sin(x), color = GOLD_A)
signal_A = MathTex(r"signal\ A", color = GOLD_B)
signal_A.scale(0.9)
signal_A_coord = axes_up.input_to_graph_point(8.5,graph_up)
signal_A.next_to(signal_A_coord,RIGHT+UP)
# Graph Down
self.play(Create(axes_down), run_time = 2)
self.play(Create(labels_down), run_time = 1)
graph_down = axes_down.plot(lambda x : np.sin(x), color = BLUE_D)
signal_B = MathTex(r"signal\ B", color = BLUE_D)
signal_B.scale(0.9)
signal_B_coord = axes_down.input_to_graph_point(8.5,graph_down)
signal_B.next_to(signal_B_coord,RIGHT+UP)
noise = axes_up.plot(lambda x : (2*np.random.rand()-1)/2,color = YELLOW_D,)
noise_label = MathTex(r"bruit", color = YELLOW_D)
noise_label_coord = axes_up.input_to_graph_point(11,graph_up)
noise_label.next_to(noise_label_coord,RIGHT+DOWN)
self.play(Create(graph_up), run_time = 2)
self.play(Create(signal_A))
self.play(Create(graph_down), run_time = 2)
self.play(Create(signal_B))
self.play(Create(noise))
self.play(Create(noise_label))
self.play(noise.animate.stretch_to_fit_height(0.5))
self.play(noise.animate.stretch_to_fit_height(1.5))
self.play(noise.animate.stretch_to_fit_height(1))
self.wait(1)
noise2=noise.copy()
noise2.generate_target()
noise2.target.shift(3.8*DOWN)
self.play(MoveToTarget(noise2))
self.wait(1)
self.play(noise2.animate.flip(RIGHT).set_color(ORANGE))
self.wait(2)
plus = MathTex(r"+", color = PURPLE_C)
plus.scale(4).shift(5*LEFT)
self.play(Create(plus))
noise2.generate_target()
noise2.target.shift(3.8*UP)
graph_down.generate_target()
graph_down.target.shift(3.7*UP)
self.play(FadeOut(axes_down,labels_down,signal_B), MoveToTarget(noise2), MoveToTarget(graph_down))
self.wait(1)
signal_AB = MathTex(r"signal\ A + B", color = GREEN_C)
signal_AB.scale(0.9)
signal_AB_coord = axes_up.input_to_graph_point(8.5,graph_up)
signal_AB.next_to(signal_AB_coord,RIGHT+UP)
self.play(FadeOut(noise, noise2, noise_label, graph_down, plus), graph_up.animate.stretch(2,1).set_color(GREEN_C), TransformMatchingTex(signal_A, signal_AB))
self.wait(3)