跳转至

声音

影片代码
import manimgx as m
import numpy as np


class SoundHero(m.Scene):
    def construct(self) -> None:
        def tone(t: np.ndarray) -> np.ndarray:
            return 0.3 * np.sin(2 * np.pi * 440 * t) * np.exp(-3 * t)

        dots = m.VGroup(
            *[m.Dot(radius=0.25, color=m.YELLOW) for _ in range(4)]
        ).arrange(buff=1)
        for dot in dots:
            self.play(
                m.FadeIn(dot, scale=2), m.Sound.of(tone, duration=0.5), run_time=0.5
            )
        self.wait()

声音从文件读取(WAV、MP3、AAC、FLAC 及其他常见种类),或由时间的函数生成。ManimGX 将每个声音放置在视频中它的时刻,并将它们混入一条音轨。

将声音连同动画交给 play,它与它们一起开始。要在任意时刻开始一个声音,添加它:场景在它播放时继续。声音播放到其结束,或直到你停止添加它时返回的片段。

声音的方法制作改变后的声音:更轻或更响、淡入或淡出、裁剪、循环、加快、平移,或在语音下压低。

Sound

影片播放的一段声音。

从文件、文件字节或采样点构造一个声音;对其进行编辑(每次编辑都会生成一个新的 声音);将其放入场景:add_sound 立即启动它且 不占用时间,而 play 将其作为 play 的一部分播放, 随后该 play 至少持续与声音一样长的时间。在组合中,它在其窗口 打开时启动:LaggedStart(*(AnimationGroup(FadeIn(d), click) for d in dots)) 在每个 点出现时发出咔嗒声。它不会改变屏幕上的任何内容,且 play 的 run_time 不会改变它: 声音持续其 duration。

m.Sound(source, *, rate=None)
source

文件的路径、文件的字节或采样点:从 -1 到 1 的浮点数,每个 声道一列(一维数组为单声道),按给定方式复制。

rate

采样点的速率,以每秒采样点数为单位;仅用于采样点。

源码

manimgx\audio\sound.py

def __init__(self, source: Source, *, rate: int | None = None) -> None:
    if isinstance(source, np.ndarray) != (rate is not None):
        raise ValueError("give a rate with samples, and only with samples")
    if rate is not None and (not math.isfinite(rate) or rate <= 0):
        raise ValueError("the sample rate must be finite and positive")
    if isinstance(source, str | os.PathLike) and not Path(source).is_file():
        raise FileNotFoundError(f"no sound file at {os.fspath(source)!r}")
    if isinstance(source, np.ndarray):  # its own: the samples as they were given
        source = np.array(source, copy=True, subok=True)
    self.source = source
    self.rate = rate
    self._edit = _Edit()
    super().__init__(None)

samples

其在 RATE 下的采样点:浮点数,每个声道一列(1 或 2)。

sound.samples
源码

manimgx\audio\sound.py

def samples(self) -> np.ndarray:
    """其在 `RATE` 下的采样点:浮点数,每个声道一列(1 或 2)。"""
    made = self.__dict__.get("_made")
    if made is None:
        made = self.__dict__["_made"] = _make(self)
    return made

duration

它持续多久,以秒为单位(对于循环播放直到被停止的声音为 inf)。

sound.duration
源码

manimgx\audio\sound.py

def duration(self) -> float:
    """它持续多久,以秒为单位(对于循环播放直到被停止的声音为 inf)。"""
    if self._edit.loop is not None:
        return self._edit.loop
    return len(self.samples) / RATE

run_time

其时长:声音播放与其持续时长相同。无尽声音(loop()) 无法被播放:请添加它(add_sound),然后停止它。

sound.run_time
源码

manimgx\audio\sound.py

def run_time(self) -> float:
    """其时长:声音播放与其持续时长相同。无尽声音(`loop()`)
    无法被播放:请添加它([`add_sound`][manimgx.Scene.add_sound]),然后停止它。
    """
    if self._edit.loop == math.inf:
        raise ValueError(
            "an endless sound (loop()) never ends: add it with add_sound, and stop"
            " its clip, instead of playing it"
        )
    return self.duration

gain

将此声音调响 decibels 分贝(若为负则调轻:-6 使其减半)。

sound.gain(decibels)
源码

manimgx\audio\sound.py

def gain(self, decibels: float) -> Self:
    """将此声音调响 `decibels` 分贝(若为负则调轻:-6 使其减半)。"""
    return self._but(gain=self._edit.gain + decibels)

fade_in

此声音在最初的 seconds 内从静音淡入。

sound.fade_in(seconds)
源码

manimgx\audio\sound.py

def fade_in(self, seconds: float) -> Self:
    """此声音在最初的 `seconds` 内从静音淡入。"""
    return self._but(fade_in=seconds)

fade_out

此声音在最后的 seconds 内淡出至静音。

sound.fade_out(seconds)
源码

manimgx\audio\sound.py

def fade_out(self, seconds: float) -> Self:
    """此声音在最后的 `seconds` 内淡出至静音。"""
    return self._but(fade_out=seconds)

trim

此声音从 start 秒到 end 秒的部分(None:到其结尾)。

sound.trim(start=0.0, end=None)
源码

manimgx\audio\sound.py

def trim(self, start: float = 0.0, end: float | None = None) -> Self:
    """此声音从 `start` 秒到 `end` 秒的部分(None:到其结尾)。"""
    return self._but(start=start, end=end)

loop

此声音重复播放以持续 duration 秒;None:直到影片结束,或 直到其片段被停止。

sound.loop(duration=None)
源码

manimgx\audio\sound.py

def loop(self, duration: float | None = None) -> Self:
    """此声音重复播放以持续 `duration` 秒;None:直到影片结束,或
    直到其片段被[停止][manimgx.audio.sound.Clip.stop]。"""
    return self._but(loop=math.inf if duration is None else duration)

speed

此声音以 factor 倍速播放,如同磁带一般:更高且更短。

结果速度必须为有限正数。

sound.speed(factor)
源码

manimgx\audio\sound.py

def speed(self, factor: float) -> Self:
    """此声音以 `factor` 倍速播放,如同磁带一般:更高且更短。

    结果速度必须为有限正数。
    """
    speed = self._edit.speed * factor
    if not math.isfinite(speed) or speed <= 0:
        raise ValueError("the resulting speed must be finite and positive")
    return self._but(speed=speed)

pan

将此声音从 -1(左)经 0(中)放置到 1(右)。

sound.pan(position)
源码

manimgx\audio\sound.py

def pan(self, position: float) -> Self:
    """将此声音从 -1(左)经 0(中)放置到 1(右)。"""
    return self._but(pan=position)

duck

此声音在任何语音播放时下压 decibels 分贝(当 Speech 播放时):解说下的音乐。它在语音前 DUCK_ATTACK 秒开始下压,在语音后 DUCK_RELEASE 秒回升。

sound.duck(decibels=12)
源码

manimgx\audio\sound.py

def duck(self, decibels: float = 12) -> Self:
    """此声音在任何语音播放时下压 `decibels` 分贝(当
    [`Speech`][manimgx.audio.Speech] 播放时):解说下的音乐。它在语音前
    `DUCK_ATTACK` 秒开始下压,在语音后 `DUCK_RELEASE` 秒回升。
    """
    return self._but(duck=decibels)

of

Sound.of(lambda t: 0.3 * np.sin(2 * np.pi * 440 * t), 1):一秒的 A 音。

由时间函数生成的声音:function(t),传入其采样点的 时间(秒,一个数组),返回它们的值(-1 到 1)。

Sound.of(function, duration)
源码

manimgx\audio\sound.py

@classmethod
def of(
    cls, function: Callable[[np.ndarray], np.ndarray], duration: float
) -> "Sound":
    """由时间函数生成的声音:`function(t)`,传入其采样点的
    时间(秒,一个数组),返回它们的值(-1 到 1)。

    Examples:
        `Sound.of(lambda t: 0.3 * np.sin(2 * np.pi * 440 * t), 1)`:一秒的 A 音。
    """
    t = np.arange(round(duration * RATE)) / RATE
    return cls(np.asarray(function(t), np.float32), rate=RATE)

add_sound

开始一个声音:现在,或距现在 time_offset 秒后;它不占用时间。

场景立即继续,声音在随后的内容上播放,直到其结束(或影片结束,或其 片段被停止)。由更新器调用时,它在更新器运行的时刻开始:弹跳声恰在 弹跳处响起。要等待一个声音,请改为 play 它。

self.add_sound(sound, time_offset=0, gain=None)
sound

该声音,或其文件。

time_offset

距现在多久后开始,以秒为单位。

gain

播放时增大的音量,以分贝表示(负值:更安静)。

返回 其片段:stop 它以提前结束。

源码

manimgx\scene.py

def add_sound(
    self,
    sound: str | os.PathLike[str] | Sound,
    time_offset: float = 0,
    gain: float | None = None,
) -> Clip:
    """开始一个声音:现在,或距现在 `time_offset` 秒后;它不占用时间。

    场景立即继续,声音在随后的内容上播放,直到其结束(或影片结束,或其
    片段被停止)。由更新器调用时,它在更新器运行的时刻开始:弹跳声恰在
    弹跳处响起。要等待一个声音,请改为 [`play`][manimgx.Scene.play] 它。

    Args:
        sound: 该声音,或其文件。
        time_offset: 距现在多久后开始,以秒为单位。
        gain: 播放时增大的音量,以分贝表示(负值:更安静)。

    Returns:
        其片段:[`stop`][manimgx.audio.sound.Clip.stop] 它以提前结束。
    """
    from manimgx.audio.sound import Clip, Sound

    if not isinstance(sound, Sound):
        sound = Sound(sound)
    if gain:
        sound = sound.gain(gain)
    # decoded now: a file it cannot read fails here, not at the end of the render
    _ = sound.samples
    clip = Clip(sound, clock.now + clock.rational(time_offset))
    self.film.clips.append(clip)
    return clip

Clip

影片播放的一段声音:从 start 开始,以场景时间的秒为单位,直到它结束 或被停止。add_sound 返回它。

stop

停止声音:它从现在(场景正在计算的瞬间)起在 fade 秒内淡出,此后静默。

clip.stop(fade=0.05)
源码

manimgx\audio\sound.py

def stop(self, fade: float = 0.05) -> None:
    """停止声音:它从现在(场景正在计算的瞬间)起在
    `fade` 秒内淡出,此后静默。"""
    self.end, self.fade = clock.now + clock.rational(fade), fade