#!/usr/bin/env python import numpy as np import itertools as it from copy import deepcopy import sys from fractions import Fraction, gcd from big_ol_pile_of_manim_imports import * from .inventing_math import Underbrace import random MOVIE_PREFIX = "music_and_measure/" INTERVAL_RADIUS = 6 NUM_INTERVAL_TICKS = 16 TICK_STRETCH_FACTOR = 4 INTERVAL_COLOR_PALETTE = [ "yellow", "green", "skyblue", "#AD1457", "#6A1B9A", "#26C6DA", "#FF8F00", ] def rationals(): curr = Fraction(1, 2) numerator, denominator = 1, 2 while True: yield curr if curr.numerator < curr.denominator - 1: new_numerator = curr.numerator + 1 while gcd(new_numerator, curr.denominator) != 1: new_numerator += 1 curr = Fraction(new_numerator, curr.denominator) else: curr = Fraction(1, curr.denominator + 1) def fraction_mobject(fraction): n, d = fraction.numerator, fraction.denominator return TexMobject("\\frac{%d}{%d}"%(n, d)) def continued_fraction(int_list): if len(int_list) == 1: return int_list[0] return int_list[0] + Fraction(1, continued_fraction(int_list[1:])) def zero_to_one_interval(): interval = NumberLine( radius = INTERVAL_RADIUS, interval_size = 2.0*INTERVAL_RADIUS/NUM_INTERVAL_TICKS ) interval.elongate_tick_at(-INTERVAL_RADIUS, TICK_STRETCH_FACTOR) interval.elongate_tick_at(INTERVAL_RADIUS, TICK_STRETCH_FACTOR) interval.add(TexMobject("0").shift(INTERVAL_RADIUS*LEFT+DOWN)) interval.add(TexMobject("1").shift(INTERVAL_RADIUS*RIGHT+DOWN)) return interval class LeftParen(Mobject): def generate_points(self): self.add(TexMobject("(")) self.center() def get_center(self): return Mobject.get_center(self) + 0.04*LEFT class RightParen(Mobject): def generate_points(self): self.add(TexMobject(")")) self.center() def get_center(self): return Mobject.get_center(self) + 0.04*RIGHT class OpenInterval(Mobject): def __init__(self, center_point = ORIGIN, width = 2, **kwargs): digest_config(self, kwargs, locals()) left = LeftParen().shift(LEFT*width/2) right = RightParen().shift(RIGHT*width/2) Mobject.__init__(self, left, right, **kwargs) # scale_factor = width / 2.0 # self.stretch(scale_factor, 0) # self.stretch(0.5+0.5*scale_factor, 1) self.shift(center_point) class Piano(ImageMobject): CONFIG = { "stroke_width" : 1, "invert" : False, "scale_factorue" : 0.5 } def __init__(self, **kwargs): digest_config(self, kwargs) ImageMobject.__init__(self, "piano_keyboard") jump = self.get_width()/24 self.center() self.half_note_jump = self.get_width()/24 self.ivory_jump = self.get_width()/14 def split(self): left = self.get_left()[0] keys = [] for count in range(14): key = Mobject( color = "white", stroke_width = 1 ) x0 = left + count*self.ivory_jump x1 = x0 + self.ivory_jump key.add_points( self.points[ (self.points[:,0] > x0)*(self.points[:,0] < x1) ] ) keys.append(key) return keys class Vibrate(Animation): CONFIG = { "num_periods" : 1, "overtones" : 4, "amplitude" : 0.5, "radius" : INTERVAL_RADIUS, "center" : ORIGIN, "color" : "white", "run_time" : 3.0, "rate_func" : None } def __init__(self, **kwargs): digest_config(self, kwargs) def func(x, t): return sum([ (self.amplitude/((k+1)**2.5))*np.sin(2*mult*t)*np.sin(k*mult*x) for k in range(self.overtones) for mult in [(self.num_periods+k)*np.pi] ]) self.func = func Animation.__init__(self, Mobject1D(color = self.color), **kwargs) def update_mobject(self, alpha): self.mobject.reset_points() epsilon = self.mobject.epsilon self.mobject.add_points([ [x*self.radius, self.func(x, alpha*self.run_time)+y, 0] for x in np.arange(-1, 1, epsilon/self.radius) for y in epsilon*np.arange(3) ]) self.mobject.shift(self.center) class IntervalScene(NumberLineScene): def construct(self): self.number_line = UnitInterval() self.displayed_numbers = [0, 1] self.number_mobs = self.number_line.get_number_mobjects(*self.displayed_numbers) self.add(self.number_line, *self.number_mobs) def show_all_fractions(self, num_fractions = 27, pause_time = 1.0, remove_as_you_go = True): shrink = not remove_as_you_go for fraction, count in zip(rationals(), list(range(num_fractions))): frac_mob, tick = self.add_fraction(fraction, shrink) self.wait(pause_time) if remove_as_you_go: self.remove(frac_mob, tick) def add_fraction(self, fraction, shrink = False): point = self.number_line.number_to_point(fraction) tick_rad = self.number_line.tick_size*TICK_STRETCH_FACTOR frac_mob = fraction_mobject(fraction) if shrink: scale_factor = 2.0/fraction.denominator frac_mob.scale(scale_factor) tick_rad *= scale_factor frac_mob.shift(point + frac_mob.get_height()*UP) tick = Line(point + DOWN*tick_rad, point + UP*tick_rad) tick.set_color("yellow") self.add(frac_mob, tick) return frac_mob, tick def add_fraction_ticks(self, num_fractions = 1000, run_time = 0): long_tick_size = self.number_line.tick_size*TICK_STRETCH_FACTOR all_ticks = [] for frac, count in zip(rationals(), list(range(num_fractions))): point = self.number_line.number_to_point(frac) tick_rad = 2.0*long_tick_size/frac.denominator tick = Line(point+tick_rad*DOWN, point+tick_rad*UP) tick.set_color("yellow") all_ticks.append(tick) all_ticks = Mobject(*all_ticks) if run_time > 0: self.play(ShowCreation(all_ticks)) else: self.add(all_ticks) return all_ticks def cover_fractions(self, epsilon = 0.3, num_fractions = 10, run_time_per_interval = 0.5): intervals = [] lines = [] num_intervals = 0 all_rationals = rationals() count = 0 while True: fraction = next(all_rationals) count += 1 if num_intervals >= num_fractions: break if fraction < self.number_line.left_num or fraction > self.number_line.right_num: continue num_intervals += 1 interval, line = self.add_open_interval( fraction, epsilon / min(2**count, 2**30), run_time = run_time_per_interval ) intervals.append(interval) lines.append(line) return intervals, lines def add_open_interval(self, num, width, color = None, run_time = 0): spatial_width = width*self.number_line.unit_length_to_spatial_width center_point = self.number_line.number_to_point(num) open_interval = OpenInterval(center_point, spatial_width) color = color or "yellow" interval_line = Line( center_point+spatial_width*LEFT/2, center_point+spatial_width*RIGHT/2 ) interval_line.do_in_place(interval_line.sort_points, get_norm) interval_line.set_color(color) if run_time > 0: squished_interval = deepcopy(open_interval).stretch_to_fit_width(0) self.play( Transform(squished_interval, open_interval), ShowCreation(interval_line), run_time = run_time ) self.remove(squished_interval) self.add(open_interval, interval_line) return open_interval, interval_line class TwoChallenges(Scene): def construct(self): two_challenges = TextMobject("Two Challenges", size = "\\Huge").to_edge(UP) one, two = list(map(TextMobject, ["1.", "2."])) one.shift(UP).to_edge(LEFT) two.shift(DOWN).to_edge(LEFT) notes = ImageMobject("musical_notes").scale(0.3) notes.next_to(one) notes.set_color("blue") measure = TextMobject("Measure Theory").next_to(two) probability = TextMobject("Probability") probability.next_to(measure).shift(DOWN+RIGHT) integration = TexMobject("\\int") integration.next_to(measure).shift(UP+RIGHT) arrow_to_prob = Arrow(measure, probability) arrow_to_int = Arrow(measure, integration) for arrow in arrow_to_prob, arrow_to_int: arrow.set_color("yellow") self.add(two_challenges) self.wait() self.add(one, notes) self.wait() self.add(two, measure) self.wait() self.play(ShowCreation(arrow_to_int)) self.add(integration) self.wait() self.play(ShowCreation(arrow_to_prob)) self.add(probability) self.wait() class MeasureTheoryToHarmony(IntervalScene): def construct(self): IntervalScene.construct(self) self.cover_fractions() self.wait() all_mobs = Mobject(*self.mobjects) all_mobs.sort_points() self.clear() radius = self.interval.radius line = Line(radius*LEFT, radius*RIGHT).set_color("white") self.play(DelayByOrder(Transform(all_mobs, line))) self.clear() self.play(Vibrate(rate_func = smooth)) self.clear() self.add(line) self.wait() class ChallengeOne(Scene): def construct(self): title = TextMobject("Challenge #1").to_edge(UP) start_color = Color("blue") colors = start_color.range_to("white", 6) self.bottom_vibration = Vibrate( num_periods = 1, run_time = 3.0, center = DOWN, color = start_color ) top_vibrations = [ Vibrate( num_periods = freq, run_time = 3.0, center = 2*UP, color = next(colors) ) for freq in [1, 2, 5.0/3, 4.0/3, 2] ] freq_220 = TextMobject("220 Hz") freq_r220 = TextMobject("$r\\times$220 Hz") freq_330 = TextMobject("1.5$\\times$220 Hz") freq_sqrt2 = TextMobject("$\\sqrt{2}\\times$220 Hz") freq_220.shift(1.5*DOWN) for freq in freq_r220, freq_330, freq_sqrt2: freq.shift(1.5*UP) r_constraint = TexMobject("(1