from helpers import * from animation.animation import Animation from animation.transform import * from animation.simple_animations import * from animation.playground import * from animation.continual_animation import * from topics.geometry import * from topics.characters import * from topics.functions import * from topics.fractals import * from topics.number_line import * from topics.combinatorics import * from topics.numerals import * from topics.three_dimensions import * from topics.objects import * from topics.probability import * from topics.complex_numbers import * from scene import Scene from scene.reconfigurable_scene import ReconfigurableScene from scene.zoomed_scene import * from camera import Camera from mobject import * from mobject.image_mobject import * from mobject.vectorized_mobject import * 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, "F_color" : TEAL, "C_color" : RED, "sum_color" : GREEN, "equilibrium_height" : 1.5, } def construct(self): self.add_speaker() 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): speaker = SVGMobject(file_name = "speaker") speaker.to_edge(DOWN) self.add(speaker) self.speaker = speaker def play_A440(self): randy = self.pi_creature A_label = TextMobject("A440") A_label.highlight(self.A_color) A_label.next_to(self.speaker, UP) self.broadcast( FadeIn(A_label), Succession( ApplyMethod, randy.change, "pondering", Animation, randy, Blink, randy ) ) self.set_variables_as_attrs(A_label) def measure_air_pressure(self): randy = self.pi_creature axes = Axes( 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(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 = self.A_frequency graph = self.get_wave_graph(frequency, axes) func = graph.underlying_function graph.highlight(self.A_color) pressure = TextMobject("Pressure") 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) n = 10 brace = Brace(Line( 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.play( FadeIn(pressure), ShowCreation(axes.y_axis) ) self.play( Write(time), ShowCreation(axes.x_axis) ) self.broadcast( ShowCreation(graph, run_time = 4, rate_func = None), ShowCreation(equilibrium_line), ) axes.add(equilibrium_line) self.play( randy.change, "erm", graph, GrowFromCenter(brace), Write(words) ) self.dither() graph.save_state() self.play( FadeOut(brace), FadeOut(words), VGroup(axes, graph, axes.labels).shift, 0.8*UP, graph.fade, 0.85, graph.shift, 0.8*UP, ) graph.saved_state.move_to(graph) self.set_variables_as_attrs(axes, A_graph = graph) def play_lower_pitch(self): 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): 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): 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 v_line = Line(UP, DOWN).scale(SPACE_HEIGHT).move_to(x_point) self.revert_to_original_skipping_status() self.play(GrowFromCenter(v_line)) self.play(FadeOut(v_line)) self.play(*map(ShowCreation, lines)) self.dither() self.play(MoveToTarget(sum_lines, path_arc = np.pi/4)) self.dither(2) self.play(*[ 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 sum_graph = axes.get_graph(new_func) sum_graph.highlight(self.sum_color) ##TODO self.play( self.get_graph_line_animation(self.A_axes, self.A_graph), self.get_graph_line_animation(self.D_axes, self.D_graph), self.get_graph_line_animation(axes, sum_graph.deepcopy()), ShowCreation(sum_graph), run_time = 15, rate_func = None ) self.dither() self.sum_graph = sum_graph def add_more_notes(self): axes = self.axes A_group = VGroup(self.A_axes, self.A_graph, self.A_label) D_group = VGroup(self.D_axes, self.D_graph, self.D_label) squish_group = VGroup(A_group, D_group) squish_group.generate_target() squish_group.target.stretch(0.5, 1) squish_group.target.next_to(axes, DOWN, buff = -SMALL_BUFF) for group in squish_group.target: label = group[-1] bottom = label.get_bottom() label.stretch_in_place(0.5, 0) label.move_to(bottom, DOWN) self.play(MoveToTarget(squish_group)) F_axes = self.D_axes.deepcopy() C_axes = self.A_axes.deepcopy() VGroup(F_axes, C_axes).next_to(squish_group, DOWN) F_graph = self.get_wave_graph(self.A_frequency*4.0/5, F_axes) F_graph.highlight(self.F_color) C_graph = self.get_wave_graph(self.A_frequency*6.0/5, C_axes) C_graph.highlight(self.C_color) F_label = TextMobject("F349") C_label = TextMobject("C523") for label, graph in (F_label, F_graph), (C_label, C_graph): label.scale(0.5) label.highlight(graph.get_stroke_color()) label.next_to(graph, UP, SMALL_BUFF) graphs = [self.A_graph, self.D_graph, F_graph, C_graph] def new_sum_func(x): result = sum([ graph.underlying_function(x) - self.equilibrium_height for graph in graphs ]) result *= 0.5 return result + self.equilibrium_height new_sum_graph = self.axes.get_graph( new_sum_func, num_graph_points = 200 ) new_sum_graph.highlight(BLUE_C) self.play(*it.chain( map(ShowCreation, [F_axes, C_axes, F_graph, C_graph,]), map(Write, [F_label, C_label]), [FadeOut(self.sum_graph)] )) kwargs = {"rate_func" : None, "run_time" : 10} self.play(ShowCreation(new_sum_graph.copy(), **kwargs), *[ self.get_graph_line_animation(curr_axes, graph, **kwargs) for curr_axes, graph in [ (self.A_axes, self.A_graph), (self.D_axes, self.D_graph), (F_axes, F_graph), (C_axes, C_graph), (axes, new_sum_graph), ] ]) self.dither() #### def broadcast(self, *added_anims, **kwargs): self.play(self.get_broadcast_animation(**kwargs), *added_anims) 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.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 + 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) def get_graph_line_animation(self, axes, graph, **kwargs): line = self.get_graph_v_line(0, axes, graph) x_max = axes.x_max def update_line(line, alpha): x = alpha*x_max Transform(line, self.get_graph_v_line(x, axes, graph)).update(1) return line return UpdateFromAlphaFunc(line, update_line, **kwargs) class BreakApartSum(Scene): CONFIG = { "frequencies" : [0.5, 1.5, 2, 2.5, 5], "equilibrium_height" : 2.0, } def construct(self): self.show_initial_sound() self.decompose_sound() self.ponder_question() def show_initial_sound(self): def func(x): return self.equilibrium_height + 0.2*np.sum([ np.cos(2*np.pi*f*x) for f in self.frequencies ]) axes = Axes( x_min = 0, x_max = 5, y_min = -1, y_max = 5, x_axis_config = { "include_tip" : False, "unit_size" : 2.0, }, y_axis_config = { "include_tip" : False, "unit_size" : 0.5, }, ) axes.stretch_to_fit_width(2*SPACE_WIDTH - 2) axes.stretch_to_fit_height(3) axes.center() axes.to_edge(LEFT) graph = axes.get_graph(func, num_graph_points = 200) graph.highlight(YELLOW) v_line = Line(ORIGIN, 4*UP) v_line.move_to(axes.coords_to_point(0, 0), DOWN) dot = Dot(color = PINK) dot.move_to(graph.point_from_proportion(0)) self.add(axes, graph) self.play( v_line.move_to, axes.coords_to_point(5, 0), DOWN, MoveAlongPath(dot, graph), run_time = 8, rate_func = None, ) self.play(*map(FadeOut, [dot, v_line])) self.set_variables_as_attrs(axes, graph) def decompose_sound(self): axes, graph = self.axes, self.graph pure_graphs = VGroup(*[ axes.get_graph( lambda x : 0.5*np.cos(2*np.pi*freq*x), num_graph_points = 100, ) for freq in self.frequencies ]) pure_graphs.gradient_highlight(BLUE, RED) pure_graphs.arrange_submobjects(DOWN, buff = MED_LARGE_BUFF) h_line = DashedLine(6*LEFT, 6*RIGHT) self.play( FadeOut(axes), graph.to_edge, UP ) pure_graphs.next_to(graph, DOWN, LARGE_BUFF) h_line.next_to(graph, DOWN, MED_LARGE_BUFF) self.play(ShowCreation(h_line)) for pure_graph in reversed(pure_graphs): self.play(ReplacementTransform(graph.copy(), pure_graph)) self.dither() self.all_graphs = VGroup(graph, h_line, pure_graphs) self.pure_graphs = pure_graphs def ponder_question(self): all_graphs = self.all_graphs pure_graphs = self.pure_graphs randy = Randolph() randy.to_corner(DOWN+LEFT) self.play( FadeIn(randy), all_graphs.scale, 0.75, all_graphs.to_corner, UP+RIGHT, ) self.play(randy.change, "pondering", all_graphs) self.play(Blink(randy)) rect = SurroundingRectangle(pure_graphs, color = WHITE) self.play( ShowCreation(rect), LaggedStart( ApplyFunction, pure_graphs, lambda g : (lambda m : m.shift(SMALL_BUFF*UP).highlight(YELLOW), g), rate_func = wiggle ) ) self.play(FadeOut(rect)) self.play(Blink(randy)) self.dither() class Quadrant(Mobject1D): CONFIG = { "radius" : 2, "stroke_width": 2, "density" : 75, } def generate_points(self): self.add_points([ r*(np.cos(theta)*RIGHT + np.sin(theta)*UP) for r in np.arange(0, self.radius, self.epsilon) for theta in np.arange(0, np.pi/2, self.epsilon/r) ]) class UnmixMixedPaint(Scene): CONFIG = { "colors" : [BLUE, RED, YELLOW, GREEN], } def construct(self): angles = np.arange(4)*np.pi/2 quadrants = PMobject(*[ Quadrant().rotate(angle).highlight(color) for color, angle in zip(self.colors, angles) ]) quadrants.ingest_submobjects() quadrants.sort_points(lambda p : np.random.random()) start_rgbas = np.array(quadrants.rgbas) target_rgbas = np.zeros(start_rgbas.shape) mud_color = average_color(*self.colors) target_rgbas[:,:] = color_to_rgba(mud_color) background_circle = Circle( radius = 2, stroke_width = 0, fill_color = mud_color, fill_opacity = 1 ) def update_color(quadrants, alpha): quadrants.rgbas = interpolate(start_rgbas, target_rgbas, alpha) self.add(background_circle, quadrants) self.play( PhaseFlow( lambda p : rotate_vector(p, np.pi/2+0.01)/(max(np.linalg.norm(p), 0.001)), quadrants, virtual_time = 20, ), UpdateFromAlphaFunc(quadrants, update_color), run_time = 10, )