muvid.choreo.scene

The scene compiler: events + sections -> a list of drawable OBJECTS.

An Obj is one thing on screen with a life: born at t_born, dead at t_die, a shape kind, a position, a size, a colour and a motion that says how it moves in between. Every number is computed here, in Python, from the event list and the treatment; the renderer only draws.

Each archetype is a function (events, section, ...) -> [Obj] in a registry (ARCHETYPE_FNS), the same seam muvid.lyricvid.scene uses: adding a look is adding a function and a vocabulary entry. The four shipped ones are the closed set in muvid.choreo.spec.ARCHETYPES.

Determinism is a design rule, not a hope. The only “randomness” is unit_hash(), a closed-form integer hash of (seed, index, ...) — so the same audio and seed produce the same objects on any machine, a re-render after a palette change moves nothing, and a test can pin a coordinate.

Positions are normalised (x in fractions of width, y of height, origin top-left) and sizes are fractions of canvas height, so one scene renders at any resolution. Velocities are in the same fractions per second.

Module Attributes

KINDS

Shape classes the renderer knows how to draw.

MOTIONS

How an object moves between birth and death.

Functions

compile_scene(treatment, analysis, *[, ...])

Every object and backdrop for the whole song.

register_archetype(name)

Register an archetype under name.

unit_hash(seed, *keys)

A float in [0, 1) that is a pure function of its integer arguments.

Classes

Backdrop(*, start, end, kind, top, bottom)

What is behind the objects during [start, end).

Canvas(*[, width, height, fps])

Output geometry.

ChoreoScene(*, canvas, duration, backdrops, ...)

Everything the renderer needs, and nothing it has to interpret.

Obj(*, t_born, t_die, kind, x, y, size, colour)

One drawable thing with a life.

class muvid.choreo.scene.Backdrop(*, start, end, kind, top, bottom)[source]

Bases: object

What is behind the objects during [start, end).

class muvid.choreo.scene.Canvas(*, width=1920, height=1080, fps=30)[source]

Bases: object

Output geometry.

class muvid.choreo.scene.ChoreoScene(*, canvas, duration, backdrops, objects, meta=<factory>)[source]

Bases: object

Everything the renderer needs, and nothing it has to interpret.

muvid.choreo.scene.KINDS = ('circle', 'rect', 'triangle', 'line', 'diamond', 'ring')

Shape classes the renderer knows how to draw.

muvid.choreo.scene.MOTIONS = ('hold', 'scroll', 'pulse', 'drift', 'flicker')

How an object moves between birth and death.

class muvid.choreo.scene.Obj(*, t_born, t_die, kind, x, y, size, colour, motion='hold', band='', strength=1.0, aspect=1.0, angle=0.0, vx=0.0, vy=0.0, attack_s=0.0, release_s=0.0, layer=0, seed=0)[source]

Bases: object

One drawable thing with a life.

Parameters:
  • y (float) – centre, normalised (x of width, y of height), at birth.

  • size (float) – the shape’s main dimension as a fraction of canvas height — diameter for circle/ring/diamond, height for rect/ triangle, length for line.

  • aspect (float) – width/height for rect/triangle; length/thickness for line.

  • angle (float) – degrees, for line.

  • vy (float) – velocity (fractions per second) for scroll/drift.

  • release_s (float) – alpha ramps at the start and end of life.

  • seed (int) – per-object key for flicker’s per-frame jitter.

muvid.choreo.scene.compile_scene(treatment, analysis, *, canvas=None, seed=0)[source]

Every object and backdrop for the whole song. Deterministic given seed.

Return type:

ChoreoScene

>>> from muvid.choreo.analysis import Analysis, Event, Section, Tempo
>>> a = Analysis(duration=4.0, tempo=Tempo(bpm=120, beats=(0, .5, 1, 1.5), source='numpy'),
...              events=(Event(t=1.0, band='low', strength=0.9),
...                      Event(t=2.0, band='high', strength=0.5)),
...              sections=(Section(index=0, label='mid', start=0.0, end=4.0, energy_db=-10),))
>>> s = compile_scene(spec_mod.default_treatment('fischinger'), a, seed=1)
>>> [(o.t_born, o.kind, o.band) for o in s.objects]
[(1.0, 'circle', 'low'), (2.0, 'triangle', 'high')]
>>> compile_scene(spec_mod.default_treatment('fischinger'), a, seed=1) == s
True
muvid.choreo.scene.register_archetype(name)[source]

Register an archetype under name.

Return type:

Callable[[Callable[..., list[Obj]]], Callable[..., list[Obj]]]

>>> @register_archetype('doctest-demo')
... def _demo(events, **kw): return []
>>> 'doctest-demo' in ARCHETYPE_FNS
True
>>> del ARCHETYPE_FNS['doctest-demo']
muvid.choreo.scene.unit_hash(seed, *keys)[source]

A float in [0, 1) that is a pure function of its integer arguments.

splitmix64-style mixing; no state, no platform dependence, so it is the only “random” this package allows itself.

Return type:

float

>>> unit_hash(0, 1) == unit_hash(0, 1), 0.0 <= unit_hash(3, 4, 5) < 1.0
(True, True)
>>> unit_hash(0, 1) != unit_hash(1, 1)
True