From 016e2c9adafba3b78b16adbd8d791bef4194dbaf Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Sat, 13 Jan 2018 15:51:25 -0800 Subject: [PATCH] Further additions to AddingPureFrequencies Scene of Fourier --- active_projects/fourier.py | 311 ++++++++++++++++++++++++++++++++----- 1 file changed, 272 insertions(+), 39 deletions(-) diff --git a/active_projects/fourier.py b/active_projects/fourier.py index ca667664..a7562629 100644 --- a/active_projects/fourier.py +++ b/active_projects/fourier.py @@ -28,16 +28,27 @@ from mobject.svg_mobject import * from mobject.tex_mobject import * from topics.graph_scene import * +#revert_to_original_skipping_status + class AddingPureFrequencies(PiCreatureScene): + CONFIG = { + "A_frequency" : 2.1, + "A_color" : YELLOW, + "D_color" : PINK, + "sum_color" : GREEN, + "equilibrium_height" : 1.5, + } def construct(self): self.force_skipping() self.add_speaker() - self.play_a440() + self.play_A440() self.measure_air_pressure() self.play_lower_pitch() self.play_mix() self.separate_out_parts() + self.draw_sum_at_single_point() + self.draw_full_sum() self.add_more_notes() def add_speaker(self): @@ -47,49 +58,59 @@ class AddingPureFrequencies(PiCreatureScene): self.add(speaker) self.speaker = speaker - def play_a440(self): + def play_A440(self): randy = self.pi_creature - a440_label = TextMobject("A440") - a440_label.next_to(self.speaker, UP) + A_label = TextMobject("A440") + A_label.highlight(self.A_color) + A_label.next_to(self.speaker, UP) self.broadcast( - FadeIn(a440_label), + FadeIn(A_label), randy.change, "pondering" ) self.dither() - self.set_variables_as_attrs(a440_label) + self.set_variables_as_attrs(A_label) def measure_air_pressure(self): randy = self.pi_creature axes = Axes( - y_min = -1, y_max = 3, + y_min = -2, y_max = 2, x_min = 0, x_max = 10, number_line_config = {"include_tip" : False}, ) axes.stretch_to_fit_height(2) axes.to_corner(UP+LEFT) - axes.shift(MED_LARGE_BUFF*DOWN) + axes.shift(LARGE_BUFF*DOWN) + eh = self.equilibrium_height + equilibrium_line = DashedLine( + axes.coords_to_point(0, eh), + axes.coords_to_point(axes.x_max, eh), + stroke_width = 2, + stroke_color = LIGHT_GREY + ) - frequency = 1.2 - func = self.get_wave_func(frequency, axes) - graph = axes.get_graph(func) - graph.highlight(YELLOW) + frequency = self.A_frequency + graph = self.get_wave_graph(frequency, axes) + func = graph.underlying_function + graph.highlight(self.A_color) pressure = TextMobject("Pressure") - pressure.next_to(axes.y_axis, UP) - pressure.shift_onto_screen() time = TextMobject("Time") + for label in pressure, time: + label.scale_in_place(0.8) + pressure.next_to(axes.y_axis, UP) + pressure.to_edge(LEFT, buff = MED_SMALL_BUFF) time.next_to(axes.x_axis.get_right(), DOWN+LEFT) axes.labels = VGroup(pressure, time) - axes.add(axes.labels) + n = 10 brace = Brace(Line( - axes.coords_to_point(7/frequency, func(7/frequency)), - axes.coords_to_point(8/frequency, func(8/frequency)), + axes.coords_to_point(n/frequency, func(n/frequency)), + axes.coords_to_point((n+1)/frequency, func((n+1)/frequency)), ), UP) words = brace.get_text("Imagine 440 per second", buff = SMALL_BUFF) + words.scale(0.8, about_point = words.get_bottom()) - self.revert_to_original_skipping_status() self.play( FadeIn(pressure), ShowCreation(axes.y_axis) @@ -100,9 +121,11 @@ class AddingPureFrequencies(PiCreatureScene): ) self.dither() self.broadcast( + randy.change, "erm", graph, ShowCreation(graph, run_time = 4, rate_func = None), - randy.change, "erm", graph + ShowCreation(equilibrium_line), ) + axes.add(equilibrium_line) self.play( GrowFromCenter(brace), Write(words) @@ -112,50 +135,231 @@ class AddingPureFrequencies(PiCreatureScene): self.play( FadeOut(brace), FadeOut(words), - graph.fade, 0.7, + VGroup(axes, graph, axes.labels).shift, 0.8*UP, + graph.fade, 0.85, + graph.shift, 0.8*UP, ) - self.set_variables_as_attrs( - axes, - A440_func = func, - A440_graph = graph, - ) + graph.saved_state.move_to(graph) + self.set_variables_as_attrs(axes, A_graph = graph) def play_lower_pitch(self): - pass + axes = self.axes + randy = self.pi_creature + + frequency = self.A_frequency*(2.0/3.0) + graph = self.get_wave_graph(frequency, axes) + graph.set_color(self.D_color) + + D_label = TextMobject("D294") + D_label.highlight(self.D_color) + D_label.move_to(self.A_label) + + self.play( + FadeOut(self.A_label), + GrowFromCenter(D_label), + ) + self.broadcast( + ShowCreation(graph, run_time = 4, rate_func = None), + randy.change, "happy", + n_circles = 6, + ) + self.play(randy.change, "confused", graph) + self.dither(2) + + self.set_variables_as_attrs( + D_label, + D_graph = graph + ) def play_mix(self): - pass + self.A_graph.restore() + self.broadcast( + self.get_broadcast_animation(n_circles = 6), + self.pi_creature.change, "thinking", + *[ + ShowCreation(graph, run_time = 4, rate_func = None) + for graph in self.A_graph, self.D_graph + ] + ) + self.dither() def separate_out_parts(self): - pass + axes = self.axes + speaker = self.speaker + randy = self.pi_creature + + A_axes = axes.deepcopy() + A_graph = self.A_graph + A_label = self.A_label + D_axes = axes.deepcopy() + D_graph = self.D_graph + D_label = self.D_label + movers = [A_axes, A_graph, A_label, D_axes, D_graph, D_label] + for mover in movers: + mover.generate_target() + D_target_group = VGroup(D_axes.target, D_graph.target) + A_target_group = VGroup(A_axes.target, A_graph.target) + D_target_group.next_to(axes, DOWN, MED_LARGE_BUFF) + A_target_group.next_to(D_target_group, DOWN, MED_LARGE_BUFF) + A_label.fade(1) + A_label.target.next_to(A_graph.target, UP) + D_label.target.next_to(D_graph.target, UP) + + self.play(*it.chain( + map(MoveToTarget, movers), + [ + ApplyMethod(mob.shift, SPACE_HEIGHT*DOWN, remover = True) + for mob in randy, speaker + ] + )) + self.dither() + + self.set_variables_as_attrs(A_axes, D_axes) + + def draw_sum_at_single_point(self): + axes = self.axes + A_axes = self.A_axes + D_axes = self.D_axes + A_graph = self.A_graph + D_graph = self.D_graph + + x = 2.85 + A_line = self.get_A_graph_v_line(x) + D_line = self.get_D_graph_v_line(x) + lines = VGroup(A_line, D_line) + sum_lines = lines.copy() + sum_lines.generate_target() + self.stack_v_lines(x, sum_lines.target) + + top_axes_point = axes.coords_to_point(x, self.equilibrium_height) + x_point = np.array(top_axes_point) + x_point[1] = 0 + l_rect, r_rect = rects = VGroup(*[ + FullScreenFadeRectangle().next_to(x_point, vect, SMALL_BUFF) + for vect in LEFT, RIGHT + ]) + rects.save_state() + for rect, vect in zip(rects, [LEFT, RIGHT]): + rect.next_to(SPACE_WIDTH*vect, vect) + + self.play(rects.restore) + self.play(*map(ShowCreation, lines)) + self.dither() + self.play(MoveToTarget(sum_lines, path_arc = np.pi/4)) + self.dither(2) + self.play( + l_rect.next_to, SPACE_WIDTH*LEFT, LEFT, + r_rect.next_to, SPACE_WIDTH*RIGHT, RIGHT, + *[ + Transform( + line, + VectorizedPoint(axes.coords_to_point(0, self.equilibrium_height)), + remover = True + ) + for line, axes in [ + (A_line, A_axes), + (D_line, D_axes), + (sum_lines, axes), + ] + ] + ) + + def draw_full_sum(self): + axes = self.axes + + def new_func(x): + result = self.A_graph.underlying_function(x) + result += self.D_graph.underlying_function(x) + result -= self.equilibrium_height + return result + + graph = axes.get_graph(new_func) + graph.highlight(self.sum_color) + graph_copy = graph.deepcopy() + + all_v_lines = VGroup( + self.get_A_graph_v_line(0), + self.get_D_graph_v_line(0), + self.get_graph_v_line(0, axes, graph) + ) + x_max = axes.x_max + def update_all_v_lines(v_lines, alpha): + A_line, D_line, sum_line = v_lines + x = alpha*x_max + Transform(A_line, self.get_A_graph_v_line(x)).update(1) + Transform(D_line, self.get_D_graph_v_line(x)).update(1) + Transform( + sum_line, + self.get_graph_v_line(x, axes, graph_copy) + ).update(1) + return v_lines + + self.play( + UpdateFromAlphaFunc(all_v_lines, update_all_v_lines), + ShowCreation(graph), + run_time = 15, + rate_func = None + ) + self.dither() def add_more_notes(self): - pass + axes = self.axes + A_label = self.A_label + D_label = self.D_label + + A_group = VGroup(self.A_axes, self.A_graph) + D_group = VGroup(self.D_axes, self.D_graph) #### def broadcast(self, *added_anims, **kwargs): - kwargs["run_time"] = kwargs.get("run_time", 5) - kwargs["n_circles"] = kwargs.get("n_circles", 8) - self.play( - Broadcast(self.speaker[1], **kwargs), - *added_anims - ) + self.play(self.get_broadcast_animation(**kwargs), *added_anims) - def get_wave_func(self, frequency, axes): + def get_broadcast_animation(self, **kwargs): + kwargs["run_time"] = kwargs.get("run_time", 5) + kwargs["n_circles"] = kwargs.get("n_circles", 10) + return Broadcast(self.speaker[1], **kwargs) + + def get_wave_graph(self, frequency, axes): tail_len = 3.0 x_min, x_max = axes.x_min, axes.x_max def func(x): - value = 0.8*np.cos(2*np.pi*frequency*x) + value = 0.7*np.cos(2*np.pi*frequency*x) if x - x_min < tail_len: value *= smooth((x-x_min)/tail_len) if x_max - x < tail_len: value *= smooth((x_max - x )/tail_len) - return value + 1.5 - return func + return value + self.equilibrium_height + ngp = 2*(x_max - x_min)*frequency + 1 + graph = axes.get_graph(func, num_graph_points = ngp) + return graph + def get_A_graph_v_line(self, x): + return self.get_graph_v_line(x, self.A_axes, self.A_graph) + + def get_D_graph_v_line(self, x): + return self.get_graph_v_line(x, self.D_axes, self.D_graph) + + def get_graph_v_line(self, x, axes, graph): + result = Line( + axes.coords_to_point(x, self.equilibrium_height), + # axes.coords_to_point(x, graph.underlying_function(x)), + graph.point_from_proportion(float(x)/axes.x_max), + color = WHITE, + buff = 0, + ) + return result + + def stack_v_lines(self, x, lines): + point = self.axes.coords_to_point(x, self.equilibrium_height) + A_line, D_line = lines + A_line.shift(point - A_line.get_start()) + D_line.shift(A_line.get_end()-D_line.get_start()) + A_line.highlight(self.A_color) + D_line.highlight(self.D_color) + return lines def create_pi_creature(self): return Randolph().to_corner(DOWN+LEFT) @@ -168,3 +372,32 @@ class AddingPureFrequencies(PiCreatureScene): + + + + + + + + + + + + + + + + + + + + + + + + + + + + +