This article records how I created a polyBeat animation using manim.

the video is requiring below a few items.

  1. in my below code, the beats are 3,4,6,8,12, the embed youtube beat rate is different but same renderring logic
  2. the sound effect is seperated
###PolyBeat 3,4,6,8,12

from manim import *
from os import system
import os
from colour import Color

config.media_dir = "E:\PythonMedia\media"
config.frame_width = 9
config.frame_height = 16
config.frame_size = (720,1280)
config.frame_rate = 50
config.background_color = BLACK

class CirclePolyBeat_3_4_6_8_12(Scene):
    restart = True
    ballPositions = []
    pattern = []
    lines = []
    speedings = []

    frameIndex = 1
    frameIndexBook = []
    ballPlay = []

    def construct(self):

        author = Text("Wangbw", color=WHITE).move_to(DOWN*5)
        self.add(author)

        colors = [Color("#be0aff"),Color("#580aff"),Color("#147df5"),Color("#0aefff"),Color("#0aff99"),
                  Color("#a1ff0a"),Color("#deff0a"),Color("#ffd300"),Color("#ff8700"),Color("#ff0000")]


        circleFake = Circle(radius=4.2, stroke_color=WHITE, fill_opacity=0, stroke_width=10,
                            stroke_opacity=0.7 ).rotate(PI / 2)
        self.add(circleFake)
        circle = Circle(radius=4, stroke_color=WHITE, fill_opacity=0).rotate(PI/2)
        #self.add(circle)

        speeding = 0.01
        def draw_collection():
            count = len(self.pattern)
            ballBook = []
            if self.restart == True:
                self.ballPositions = []
                self.lines = []
                self.speedings = []
                for x in self.pattern:
                    self.ballPositions.append(0)
                    self.speedings.append(speeding * x)

                for index in range(len(self.pattern)):
                    tmp = Line(start=circle.point_from_proportion(index * 1 / count), end=[0, 0, 0],
                                stroke_width=10, stroke_opacity=0.8)
                    line = Line(start=tmp.point_from_proportion(0), end=tmp.point_from_proportion(0.95),
                                stroke_width=10, stroke_opacity=0.8)
                    self.lines.append(line)

            vg = VGroup()

            for x in self.lines:
                vg.add(x)

            ###run ball
            for index in range(len(self.pattern)):
                temp = self.ballPositions[index] + self.speedings[index]
                width = 0
                if temp<0:
                    temp = - temp
                    self.speedings[index] = -self.speedings[index]
                if temp>1:
                    temp = 2 - temp
                    self.speedings[index] = -self.speedings[index]
                    ##play sound
                    circleFake.set_style(stroke_color=colors[index])
                    ballBook.append(self.pattern[index])
                if temp > 0.90:
                    width = 2
                self.ballPositions[index] = temp
                ball = Circle(radius=0.2, fill_opacity=1, color=colors[index],stroke_width=width, stroke_color=WHITE).move_to(self.lines[index].point_from_proportion(self.ballPositions[index]))
                vg.add(ball)

            centerCircle = Circle(radius=0.15, color=RED, fill_opacity=1)
            vg.add(centerCircle)

            self.restart= False


            if len(ballBook)>0 :
                if len(self.frameIndexBook)>0:
                    if self.frameIndex - self.frameIndexBook[len(self.frameIndexBook) -1 ] == 1:
                        print(self.frameIndex)
                        if len(ballBook) >= len(self.ballPlay[len(self.ballPlay)-1]):
                            self.frameIndexBook[len(self.frameIndexBook) -1 ] = self.frameIndex
                            for x in ballBook:
                                self.ballPlay[len(self.ballPlay) - 1].append(x)
                        else:
                            for x in ballBook:
                                self.ballPlay[len(self.ballPlay) - 1].append(x)
                    else:
                        self.ballPlay.append(ballBook)
                        self.frameIndexBook.append(self.frameIndex)
                else:
                    self.ballPlay.append(ballBook)
                    self.frameIndexBook.append(self.frameIndex)
            self.frameIndex = self.frameIndex + 1

            return vg

        vg_mobject = always_redraw(draw_collection)
        self.add(vg_mobject)

        playBook = [[3],[3,4],[3,4,6],[3,4,6,12],[3,4,8],[3,4,12],[3,4,6,8,12],[3,4,6]]
        #playBook = [[3], [3, 4], [3, 4, 6]]
        info = Text("{}".format(self.pattern)).move_to(UP * 5)
        self.add(info)
        for book in playBook:
            self.restart = True

            self.pattern = book
            runTime = 0

            if len(book)<=2:
                runTime=2
            if len(book)>=3:
                runTime=60
            self.remove(info)
            info = Text("{}".format(self.pattern)).move_to(UP*5)
            self.add(info)

            self.play(Create(Text("").move_to(LEFT*6), run_time=runTime))
        print(self.frameIndexBook)
        print(self.ballPlay)
if __name__ == '__main__':
    system("manim -ql {} CirclePolyBeat_3_4_6_8_12 --disable_caching".format(__file__))


By Admin

Think-Math Website