framerudiments 0.4.4__tar.gz → 0.4.6__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {framerudiments-0.4.4/src/framerudiments.egg-info → framerudiments-0.4.6}/PKG-INFO +28 -1
- {framerudiments-0.4.4 → framerudiments-0.4.6}/README.md +27 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/RELEASE_NOTES.md +50 -0
- framerudiments-0.4.6/docs/media/palette-grade-comparison.png +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/docs/project-format.md +4 -1
- {framerudiments-0.4.4 → framerudiments-0.4.6}/docs/rich-lyric-effects.md +9 -1
- {framerudiments-0.4.4 → framerudiments-0.4.6}/docs/visualiser-routing.md +8 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/docs/visualisers.md +62 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/pyproject.toml +1 -1
- {framerudiments-0.4.4 → framerudiments-0.4.6}/src/framerudiments/__init__.py +1 -1
- {framerudiments-0.4.4 → framerudiments-0.4.6}/src/framerudiments/compositor.py +5 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/src/framerudiments/encoding.py +15 -1
- {framerudiments-0.4.4 → framerudiments-0.4.6}/src/framerudiments/lyric_indicator.py +46 -2
- {framerudiments-0.4.4 → framerudiments-0.4.6}/src/framerudiments/projectm_backend.py +19 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/src/framerudiments/timeline.py +15 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/src/framerudiments/visualiser_assets/butterchurn-page.js +3 -2
- {framerudiments-0.4.4 → framerudiments-0.4.6}/src/framerudiments/visualiser_routing.py +26 -4
- {framerudiments-0.4.4 → framerudiments-0.4.6}/src/framerudiments/visualisers.py +174 -15
- {framerudiments-0.4.4 → framerudiments-0.4.6/src/framerudiments.egg-info}/PKG-INFO +28 -1
- {framerudiments-0.4.4 → framerudiments-0.4.6}/src/framerudiments.egg-info/SOURCES.txt +2 -0
- framerudiments-0.4.6/tests/test_encoding.py +33 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/tests/test_rich_subtitles.py +24 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/tests/test_visualiser_routing.py +109 -1
- {framerudiments-0.4.4 → framerudiments-0.4.6}/tests/test_visualisers.py +90 -2
- {framerudiments-0.4.4 → framerudiments-0.4.6}/AGENTS.md +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/LICENSE +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/MANIFEST.in +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/analysis-releases.json +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/docs/ECOSYSTEM.md +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/docs/FrameRudiments-PATCH_WORKFLOW.md +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/docs/annular-modulation.md +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/docs/authoring-workspace.md +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/docs/beat-alignment.md +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/docs/controlled-audio-response.md +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/docs/expanded-effects.md +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/docs/extraction.md +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/docs/full-preset-analysis.md +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/docs/hybrid-preset-analysis.md +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/docs/keyframed-mattes.md +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/docs/media/beat-alignment-evidence.json +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/docs/media/beat-alignment.mp4 +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/docs/media/beat-alignment.png +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/docs/media/demo-transcript.md +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/docs/media/effects-pipeline.svg +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/docs/media/full-frame-comparison.png +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/docs/media/full-frame-demo.mp4 +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/docs/media/media-evidence.json +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/docs/media/natural-materials.mp4 +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/docs/media/natural-materials.png +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/docs/media/preset-test-cards.png +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/docs/media/rangoli-transcript.md +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/docs/media/rangoli.mp4 +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/docs/media/rangoli.png +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/docs/media/test-card-inputs/colour-bars.png +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/docs/media/test-card-inputs/grid-rings.png +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/docs/media/test-card-inputs/text-card.png +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/docs/multi-stem-modulation.md +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/docs/music-reactive-3d.md +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/docs/natural-materials.md +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/docs/particle-smoke.md +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/docs/phase3-compositing.md +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/docs/preset-behaviour.md +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/docs/preset-profiling.md +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/docs/projectm-local-runtime.md +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/docs/quality-effects.md +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/docs/rangoli.md +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/docs/rudiment-renderers.md +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/docs/scoped-post-effects.md +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/docs/solid-spatial-renderers.md +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/docs/spatial-renderers.md +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/docs/spatiotemporal-supersampling.md +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/docs/spectrum.md +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/docs/sprite-visualisations.md +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/docs/stem-sources-and-masks.md +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/docs/variable-bpm-word-subtitles.md +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/docs/visualisation-atlas.md +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/docs/visualisation-coverage-matrix.md +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/docs/workflow-media.md +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/integration/test_fire_volume_gpu.py +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/integration/test_music_spatial_gpu.py +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/integration/test_preset_analysis.py +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/integration/test_released_preset_analysis.py +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/integration/test_visualisers_gpu.py +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/scripts/Run-ControlledPresetAnalysis.ps1 +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/scripts/Run-FullPresetAnalysis.ps1 +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/scripts/Run-HybridPresetAnalysis.ps1 +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/scripts/Run-PresetBehaviourAnalysis.ps1 +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/scripts/authoring_preview.py +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/scripts/bake_mantaflow_fire.py +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/scripts/build_sprite_assets.py +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/scripts/check_projectm_runtime.py +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/scripts/check_release_packages.py +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/scripts/controlled_preset_analysis.py +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/scripts/ensure_projectm_runtime.py +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/scripts/full_preset_analysis.py +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/scripts/generate_particle_gallery.py +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/scripts/generate_post_effect_gallery.py +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/scripts/generate_quality_gallery.py +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/scripts/generate_rich_lyric_gallery.py +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/scripts/generate_sprite_gallery.py +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/scripts/generate_supersampling_gallery.py +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/scripts/generate_timing_gallery.py +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/scripts/generate_visualisation_gallery.py +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/scripts/generate_visualiser_routing_gallery.py +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/scripts/install_analysis_releases.py +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/scripts/prepare_fire_atlas.py +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/scripts/prepare_pixelcue_taggers.py +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/scripts/preset_behaviour_pipeline.py +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/scripts/profile_visualisation_coverage.py +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/scripts/profile_visualisers.py +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/scripts/projectm_dll_diagnostics.py +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/scripts/render_natural_material_demo.py +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/scripts/repair_projectm_mingw.py +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/scripts/select_projectm_generator.py +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/scripts/validate_patch.py +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/setup.cfg +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/src/framerudiments/__main__.py +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/src/framerudiments/advanced_renderers.py +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/src/framerudiments/atlas.py +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/src/framerudiments/audio_probes.py +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/src/framerudiments/beat_alignment.py +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/src/framerudiments/beat_timeline.py +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/src/framerudiments/choreography.py +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/src/framerudiments/circular_renderers.py +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/src/framerudiments/cli.py +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/src/framerudiments/controlled_profiling.py +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/src/framerudiments/demo.py +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/src/framerudiments/effect_catalogue.py +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/src/framerudiments/effect_renderers.py +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/src/framerudiments/effects.py +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/src/framerudiments/fire_assets/mantaflow/NOTICE.md +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/src/framerudiments/fire_assets/mantaflow/atlas.json +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/src/framerudiments/fire_assets/mantaflow/flames.png +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/src/framerudiments/fire_assets/marburg/NOTICE.md +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/src/framerudiments/fire_assets/marburg/atlas.json +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/src/framerudiments/fire_assets/marburg/flames.png +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/src/framerudiments/fire_assets.py +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/src/framerudiments/fire_fluid.py +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/src/framerudiments/fire_renderer.py +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/src/framerudiments/fire_volume.py +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/src/framerudiments/flight.py +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/src/framerudiments/gpu_spatial_backend.py +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/src/framerudiments/io.py +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/src/framerudiments/keyframes.py +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/src/framerudiments/layer_animation.py +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/src/framerudiments/layer_displacement.py +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/src/framerudiments/layer_masks.py +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/src/framerudiments/modulation.py +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/src/framerudiments/motion.py +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/src/framerudiments/music_spatial_renderers.py +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/src/framerudiments/natural_materials.py +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/src/framerudiments/output_quality.py +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/src/framerudiments/particle_engine.py +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/src/framerudiments/post_effects.py +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/src/framerudiments/preset_library.py +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/src/framerudiments/preset_profiling.py +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/src/framerudiments/profile_analysis.py +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/src/framerudiments/project.py +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/src/framerudiments/quality_effects.py +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/src/framerudiments/rangoli_renderer.py +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/src/framerudiments/render.py +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/src/framerudiments/rich_subtitles.py +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/src/framerudiments/rudiment_renderers.py +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/src/framerudiments/scoped_effects.py +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/src/framerudiments/solid_spatial_renderers.py +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/src/framerudiments/spatial_renderers.py +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/src/framerudiments/spectrum.py +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/src/framerudiments/sprite_assets/arcade.png +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/src/framerudiments/sprite_assets/crystal.png +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/src/framerudiments/sprite_assets/fire.png +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/src/framerudiments/sprite_assets/generated-neon.png +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/src/framerudiments/sprite_assets/glass.png +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/src/framerudiments/sprite_assets/hud.png +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/src/framerudiments/sprite_assets/ice.png +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/src/framerudiments/sprite_assets/lcd-amber.png +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/src/framerudiments/sprite_assets/lcd-green.png +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/src/framerudiments/sprite_assets/lcd.png +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/src/framerudiments/sprite_assets/led.png +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/src/framerudiments/sprite_assets/manifest.json +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/src/framerudiments/sprite_assets/metal.png +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/src/framerudiments/sprite_assets/mono.png +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/src/framerudiments/sprite_assets/neon.png +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/src/framerudiments/sprite_assets/pixel.png +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/src/framerudiments/sprite_assets/plasma.png +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/src/framerudiments/sprite_assets/vfd.png +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/src/framerudiments/sprite_catalogue.py +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/src/framerudiments/sprite_renderers.py +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/src/framerudiments/stem_blend.py +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/src/framerudiments/stemlab.py +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/src/framerudiments/subtitles.py +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/src/framerudiments/super_sampling.py +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/src/framerudiments/terrain_field.py +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/src/framerudiments/visual_style.py +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/src/framerudiments/visualisation_catalogue.py +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/src/framerudiments/visualiser_assets/butterchurn-worker.mjs +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/src/framerudiments/visualiser_assets/feedback.json +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/src/framerudiments/visualiser_assets/feedback.milk +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/src/framerudiments/visualiser_assets/julia.json +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/src/framerudiments/visualiser_assets/julia.milk +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/src/framerudiments/visualiser_assets/kaleidoscope.json +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/src/framerudiments/visualiser_assets/kaleidoscope.milk +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/src/framerudiments/visualiser_assets/package-lock.json +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/src/framerudiments/visualiser_assets/package.json +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/src/framerudiments/visualiser_assets/rangoli-background.milk +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/src/framerudiments/visualiser_setup.py +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/src/framerudiments/word_timing.py +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/src/framerudiments.egg-info/dependency_links.txt +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/src/framerudiments.egg-info/entry_points.txt +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/src/framerudiments.egg-info/requires.txt +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/src/framerudiments.egg-info/top_level.txt +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/tests/conftest.py +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/tests/test_adapters.py +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/tests/test_advanced_renderers.py +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/tests/test_authoring_preview.py +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/tests/test_beat_alignment.py +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/tests/test_behaviour_pipeline.py +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/tests/test_checkpoint_batches.py +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/tests/test_controlled_audio.py +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/tests/test_coverage_matrix.py +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/tests/test_default_antialias.py +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/tests/test_effect_batch.py +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/tests/test_extracted_helpers.py +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/tests/test_fire_methods.py +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/tests/test_flight.py +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/tests/test_full_preset_analysis.py +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/tests/test_gpu_ci_isolation.py +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/tests/test_hybrid_launcher.py +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/tests/test_keyframes.py +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/tests/test_layer_masks.py +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/tests/test_lyrics.py +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/tests/test_music_spatial_pipeline.py +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/tests/test_music_spatial_renderers.py +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/tests/test_natural_materials.py +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/tests/test_particle_engine.py +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/tests/test_patch_validation.py +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/tests/test_phase3_animation.py +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/tests/test_phase3_gpu_injection.py +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/tests/test_pipeline.py +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/tests/test_preset_profiling.py +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/tests/test_projectm_dll_diagnostics.py +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/tests/test_projectm_generator.py +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/tests/test_projectm_runtime_preflight.py +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/tests/test_pytest_temp_isolation.py +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/tests/test_quality_effects.py +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/tests/test_rangoli_renderer.py +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/tests/test_release_metadata.py +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/tests/test_repair_projectm_mingw.py +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/tests/test_rudiment_renderers.py +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/tests/test_scoped_effects.py +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/tests/test_solid_spatial_integration.py +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/tests/test_solid_spatial_renderers.py +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/tests/test_spatial_renderers.py +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/tests/test_spectrum.py +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/tests/test_sprite_renderers.py +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/tests/test_stem_blend.py +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/tests/test_stem_source_integration.py +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/tests/test_supersampling.py +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/tests/test_timeline.py +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/tests/test_variable_bpm_words.py +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/tests/test_visualisation_modulation.py +0 -0
- {framerudiments-0.4.4 → framerudiments-0.4.6}/tests/test_windows_launcher_splitpath.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: framerudiments
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.6
|
|
4
4
|
Summary: Deterministic Shorts and lyric visualisers using DanceRudiments and StemLab evidence.
|
|
5
5
|
Author: Kieran Simkin
|
|
6
6
|
License-Expression: MIT
|
|
@@ -31,6 +31,31 @@ Dynamic: license-file
|
|
|
31
31
|
|
|
32
32
|
# FrameRudiments
|
|
33
33
|
|
|
34
|
+
## Complete x264 preset range
|
|
35
|
+
|
|
36
|
+
Version 0.4.6 accepts every named libx264 preset from `ultrafast` through
|
|
37
|
+
`placebo`, including the previously rejected `slower` and `veryslow` delivery
|
|
38
|
+
profiles. The selected preset and CRF remain explicit render arguments and are
|
|
39
|
+
retained in the encode command and manifest evidence. A stronger preset can
|
|
40
|
+
improve compression efficiency; it does not raise project resolution, mesh
|
|
41
|
+
density, frame rate or source sampling on its own.
|
|
42
|
+
|
|
43
|
+
The existing encoded demonstrations below remain representative because this
|
|
44
|
+
change alters compression controls rather than composition or motion. No new
|
|
45
|
+
image or video was generated solely to illustrate byte-level encoder effort.
|
|
46
|
+
|
|
47
|
+
## Palette-controlled high-detail feedback
|
|
48
|
+
|
|
49
|
+
Version 0.4.4 adds optional Butterchurn mesh dimensions and a deterministic
|
|
50
|
+
multi-stop palette grade after each full-frame visualiser stage. This lets a
|
|
51
|
+
1080p visualiser retain more spatial detail and conform feedback colours to a
|
|
52
|
+
campaign palette while clean routed overlays keep their authored appearance.
|
|
53
|
+
See [full-frame visualisers](docs/visualisers.md).
|
|
54
|
+
|
|
55
|
+

|
|
56
|
+
|
|
57
|
+
Generated test illustration; no private campaign media is included.
|
|
58
|
+
|
|
34
59
|
## Five fire methods and ice
|
|
35
60
|
|
|
36
61
|
Version 0.4.3 adds evidence-recorded three-stop fire palettes, an optional
|
|
@@ -204,6 +229,8 @@ text. Existing plain text remains untouched unless configured. Set `word_indicat
|
|
|
204
229
|
native 1-dimensional DanceRudiment-driven bouncing sprite that tracks the active word of
|
|
205
230
|
the full line. Its particle field can be continuous, word/cue-triggered, or hybrid;
|
|
206
231
|
trigger windows independently change colour, density, size, glow and burst count.
|
|
232
|
+
An optional between-word travel arc rises before the next timestamp and lands
|
|
233
|
+
exactly on its encoded word frame.
|
|
207
234
|
See [rich lyric reference](docs/rich-lyric-effects.md) and the
|
|
208
235
|
[CI-generated demo](https://kieransimkin.github.io/FrameRudiments/rich-lyrics/).
|
|
209
236
|
|
|
@@ -1,5 +1,30 @@
|
|
|
1
1
|
# FrameRudiments
|
|
2
2
|
|
|
3
|
+
## Complete x264 preset range
|
|
4
|
+
|
|
5
|
+
Version 0.4.6 accepts every named libx264 preset from `ultrafast` through
|
|
6
|
+
`placebo`, including the previously rejected `slower` and `veryslow` delivery
|
|
7
|
+
profiles. The selected preset and CRF remain explicit render arguments and are
|
|
8
|
+
retained in the encode command and manifest evidence. A stronger preset can
|
|
9
|
+
improve compression efficiency; it does not raise project resolution, mesh
|
|
10
|
+
density, frame rate or source sampling on its own.
|
|
11
|
+
|
|
12
|
+
The existing encoded demonstrations below remain representative because this
|
|
13
|
+
change alters compression controls rather than composition or motion. No new
|
|
14
|
+
image or video was generated solely to illustrate byte-level encoder effort.
|
|
15
|
+
|
|
16
|
+
## Palette-controlled high-detail feedback
|
|
17
|
+
|
|
18
|
+
Version 0.4.4 adds optional Butterchurn mesh dimensions and a deterministic
|
|
19
|
+
multi-stop palette grade after each full-frame visualiser stage. This lets a
|
|
20
|
+
1080p visualiser retain more spatial detail and conform feedback colours to a
|
|
21
|
+
campaign palette while clean routed overlays keep their authored appearance.
|
|
22
|
+
See [full-frame visualisers](docs/visualisers.md).
|
|
23
|
+
|
|
24
|
+

|
|
25
|
+
|
|
26
|
+
Generated test illustration; no private campaign media is included.
|
|
27
|
+
|
|
3
28
|
## Five fire methods and ice
|
|
4
29
|
|
|
5
30
|
Version 0.4.3 adds evidence-recorded three-stop fire palettes, an optional
|
|
@@ -173,6 +198,8 @@ text. Existing plain text remains untouched unless configured. Set `word_indicat
|
|
|
173
198
|
native 1-dimensional DanceRudiment-driven bouncing sprite that tracks the active word of
|
|
174
199
|
the full line. Its particle field can be continuous, word/cue-triggered, or hybrid;
|
|
175
200
|
trigger windows independently change colour, density, size, glow and burst count.
|
|
201
|
+
An optional between-word travel arc rises before the next timestamp and lands
|
|
202
|
+
exactly on its encoded word frame.
|
|
176
203
|
See [rich lyric reference](docs/rich-lyric-effects.md) and the
|
|
177
204
|
[CI-generated demo](https://kieransimkin.github.io/FrameRudiments/rich-lyrics/).
|
|
178
205
|
|
|
@@ -1,3 +1,53 @@
|
|
|
1
|
+
# FrameRudiments 0.4.6
|
|
2
|
+
|
|
3
|
+
- Encoding now accepts the complete named libx264 preset range: `ultrafast`,
|
|
4
|
+
`superfast`, `veryfast`, `faster`, `fast`, `medium`, `slow`, `slower`,
|
|
5
|
+
`veryslow` and `placebo`.
|
|
6
|
+
- Focused regression coverage verifies every accepted preset reaches the exact
|
|
7
|
+
FFmpeg command and that unknown values still fail closed.
|
|
8
|
+
- Linux CI, gallery and release jobs now pin Ubuntu 24.04 so runner-image
|
|
9
|
+
migrations cannot silently change the release environment mid-validation.
|
|
10
|
+
- Linux render dependencies use one shared installer with bounded timeouts and
|
|
11
|
+
retries, preventing a stalled package mirror from hanging a release forever.
|
|
12
|
+
- Preset strength remains separate from composition resolution, visualiser
|
|
13
|
+
mesh density, frame rate and source sampling. Existing public demonstration
|
|
14
|
+
media therefore remains applicable; no new visual was generated for this
|
|
15
|
+
command-validation-only change.
|
|
16
|
+
|
|
17
|
+
# FrameRudiments 0.4.5
|
|
18
|
+
|
|
19
|
+
- Word indicators can now travel between encoded word hits with a configurable
|
|
20
|
+
arc and land on the next word's authoritative frame, while retaining the
|
|
21
|
+
existing after-hit mode.
|
|
22
|
+
- projectM stages accept source-time preset timelines and perform bounded soft
|
|
23
|
+
transitions on the existing feedback instance, with every switch recorded in
|
|
24
|
+
render evidence.
|
|
25
|
+
- Lyric layers can feed the visualiser only during their final bounded pips when
|
|
26
|
+
a real blank gap follows, allowing the departing line to dissolve into the
|
|
27
|
+
background without contaminating immediate line-to-line changes.
|
|
28
|
+
- Project-format and visualiser documentation describe the new contracts;
|
|
29
|
+
focused regression coverage preserves clock, preset trust, routing and
|
|
30
|
+
evidence behaviour. Private campaign artwork and audio remain outside the
|
|
31
|
+
distribution.
|
|
32
|
+
|
|
33
|
+
## High-detail, palette-controlled visualiser output
|
|
34
|
+
|
|
35
|
+
- Butterchurn stages accept bounded `mesh_width` and `mesh_height` values. The
|
|
36
|
+
existing 64×48 mesh remains the default; projects can request additional
|
|
37
|
+
spatial detail without changing their encoded dimensions or frame rate.
|
|
38
|
+
- Any Butterchurn or projectM stage can apply `output_palette_grade` after the
|
|
39
|
+
engine output and before the next stage or clean routing overlay. Two to
|
|
40
|
+
sixteen ordered CSS colours define a deterministic luminance palette; alpha,
|
|
41
|
+
PCM, feedback state and the source clock remain unchanged.
|
|
42
|
+
- The exact mesh and grade configuration is retained in plan and manifest
|
|
43
|
+
evidence. Focused validation covers schema bounds, deterministic pixels,
|
|
44
|
+
alpha preservation, stage order and routed overlays.
|
|
45
|
+
|
|
46
|
+

|
|
47
|
+
|
|
48
|
+
Generated test illustration; no private campaign media. A video is omitted
|
|
49
|
+
because this stateless grade adds no temporal behaviour to demonstrate.
|
|
50
|
+
|
|
1
51
|
# FrameRudiments 0.4.4
|
|
2
52
|
|
|
3
53
|
- Spectrum views accept a validated `style.grid` boolean. Set it to `false` to remove the three fixed dB guide lines while retaining calibrated bars and moving peak holds; label visibility remains independent.
|
|
Binary file
|
|
@@ -23,6 +23,7 @@ the inputs are checked again before and after a render.
|
|
|
23
23
|
| `layers` | Ordered sprite, legacy bar and modular rudiment layers described below. |
|
|
24
24
|
| Native radial petals | Use `type: "rudiment", renderer: "rangoli"` with independent scale/spin/lift axes; see [rangoli configuration and actual demo](rangoli.md). |
|
|
25
25
|
| `visualiser_routing` | Optional per-element GPU input/overlay policies. Feed continuously, only for first N native pips after visibility onset, or never; see [routing](visualiser-routing.md). |
|
|
26
|
+
| `effects` | Ordered Butterchurn/projectM stages. Butterchurn may set a denser `mesh_width`/`mesh_height`; any stage may set a multi-stop `output_palette_grade` applied before the next stage or clean routing overlay. projectM stages may use `preset_timeline` for ordered changes at canonical source seconds through the native soft transition on one retained feedback instance. See [visualisers](visualisers.md). |
|
|
26
27
|
| `lyric_art` | `{items: [{text, asset}], placement: <sprite fields>, fallback: "text" or "error"}`. Exact text lookup preserves canonical lyric association. `error` requires every visible lyric to have an asset. |
|
|
27
28
|
| `stemlab_loop` | `{file: <pinned loops report>, id: <accepted loop id>}`; imports native bounds/rate, verifies source SHA, rejects conflicting explicit audio values. Does not approve a seam. |
|
|
28
29
|
|
|
@@ -148,7 +149,9 @@ Set `effects` and optional checksum-pinned `font` on any `captions`, `word_capti
|
|
|
148
149
|
emboss, inner/outer glow and drop shadow. `word_indicator` needs both line and word LRC,
|
|
149
150
|
a full-line captions block and a selected native one-axis Rudiment bounce driver. Its
|
|
150
151
|
particle options support constant emission, timed word/cue bursts and trigger-driven
|
|
151
|
-
appearance changes
|
|
152
|
+
appearance changes. `travel_mode: "between_words"` can use the pinned next-word
|
|
153
|
+
timestamp to follow a smooth pre-hit arc and land exactly on the word frame;
|
|
154
|
+
see [full schema and examples](rich-lyric-effects.md).
|
|
152
155
|
|
|
153
156
|
## Optional full-frame effects
|
|
154
157
|
|
|
@@ -92,6 +92,8 @@ DanceRudiment axis to the y-coordinate. The cursor may use a `spark`, `orb`,
|
|
|
92
92
|
"glow": 5,
|
|
93
93
|
"opacity": 1,
|
|
94
94
|
"travel_frames": 10,
|
|
95
|
+
"travel_mode": "between_words",
|
|
96
|
+
"travel_arc": 24,
|
|
95
97
|
"offset_x": 0,
|
|
96
98
|
"offset_y": 14,
|
|
97
99
|
"bounce": {
|
|
@@ -123,7 +125,13 @@ The `sprite` field is optional. For custom files, the supplied SHA-256 is
|
|
|
123
125
|
mandatory; changing images requires changing the project reference. The same
|
|
124
126
|
built-in transparent glyphs work without external assets. `position` accepts
|
|
125
127
|
`above`, `below` or `on`. `travel_frames` (0–120) determines the smoothstep
|
|
126
|
-
movement
|
|
128
|
+
movement window. The default `travel_mode: "after_hit"` preserves the original
|
|
129
|
+
behaviour, moving from the preceding word after the new timestamp. Use
|
|
130
|
+
`travel_mode: "between_words"` to move during the final `travel_frames` before
|
|
131
|
+
the next encoded word timestamp; `travel_arc` (0–250 pixels) adds a sine-shaped
|
|
132
|
+
lift that is zero at both ends, so the sprite lands exactly on the next word
|
|
133
|
+
frame. This uses only the pinned word ledger and does not infer timing from the
|
|
134
|
+
audio. Selecting `bounce.axis` projects one native
|
|
127
135
|
DanceRudiment dimension only; captured recordings retain their own source
|
|
128
136
|
clock, and rhythmic named/table motions use the corrected BPM map if enabled.
|
|
129
137
|
|
|
@@ -55,6 +55,7 @@ misspellings are errors rather than silent omissions.
|
|
|
55
55
|
| --- | --- |
|
|
56
56
|
| `continuous` | Inject this element on every output frame where it has visible alpha pixels |
|
|
57
57
|
| `first_pips` | Inject from the first visible frame until `duration_pips` source-clock pips have elapsed, using the half-open interval `[appearance_pip, appearance_pip + duration_pips)` |
|
|
58
|
+
| `last_pips` | With `retrigger: lyric`, inject the visible lyric element for the final `duration_pips` before the next effective lyric state becomes blank. A same-frame clear followed by a new nonblank line is an immediate handoff and does not trigger this window. |
|
|
58
59
|
| `never` | No pixels from this element enter the first GPU stage; it can still be overlaid |
|
|
59
60
|
|
|
60
61
|
`duration_pips` must be a finite number between 0 and 65,536. One beat is
|
|
@@ -64,6 +65,13 @@ beat count over repeated audio excerpts. Therefore a 64-pip window may be
|
|
|
64
65
|
shorter or longer than half a second as the music accelerates or slows.
|
|
65
66
|
A zero-pip window never injects the element.
|
|
66
67
|
|
|
68
|
+
`last_pips` is designed for lyric tails that should enter feedback only before
|
|
69
|
+
a genuine silent display gap. It requires `retrigger: lyric`, an active nonblank
|
|
70
|
+
lyric, and a known next effective lyric event. The route report records
|
|
71
|
+
`remaining_pips` and `lyric_gap_exit` so the decision is auditable. With
|
|
72
|
+
`overlay: true`, the readable original remains visible while its copy enters the
|
|
73
|
+
feedback pipeline; at the blank event both disappear from the clean layer.
|
|
74
|
+
|
|
67
75
|
`retrigger` controls how the window starts:
|
|
68
76
|
|
|
69
77
|
- `reappearance` (default): reset the window when an element becomes visible
|
|
@@ -22,9 +22,71 @@ Original paired presets are `feedback` (audio-reactive swirl/echo),
|
|
|
22
22
|
distortion of the frame's texture coordinates). They are examples, not a claim
|
|
23
23
|
that two independent MilkDrop implementations produce identical pixels.
|
|
24
24
|
|
|
25
|
+
Butterchurn stages may set `mesh_width` (16–256) and `mesh_height` (12–256).
|
|
26
|
+
The default remains 64×48 for pixel compatibility. A denser mesh such as
|
|
27
|
+
128×72 gives the warp field more spatial detail at 1920×1080, but it does not
|
|
28
|
+
change the encoded dimensions or frame rate and can increase GPU cost.
|
|
29
|
+
|
|
30
|
+
Each stage may also apply a deterministic multi-stop `output_palette_grade`
|
|
31
|
+
after its visualiser output and before the next stage or the clean routing
|
|
32
|
+
overlay:
|
|
33
|
+
|
|
34
|
+
```json
|
|
35
|
+
{
|
|
36
|
+
"id": "clay-feedback",
|
|
37
|
+
"engine": "butterchurn",
|
|
38
|
+
"preset": "feedback",
|
|
39
|
+
"mesh_width": 128,
|
|
40
|
+
"mesh_height": 72,
|
|
41
|
+
"output_palette_grade": {
|
|
42
|
+
"stops": ["#100b19", "#171b37", "#572417", "#8a3c25", "#c49986"],
|
|
43
|
+
"amount": 0.85
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
The grade maps output luminance through 2–16 ordered CSS colours in
|
|
49
|
+
sRGB, blends that result with the original by `amount` (0–1), and preserves
|
|
50
|
+
alpha. It does not change PCM, motion, feedback state or the source clock.
|
|
51
|
+
When `visualiser_routing` keeps lyrics or sprites as a clean overlay, that
|
|
52
|
+
overlay is composited after the grade and retains its authored colours.
|
|
53
|
+
|
|
54
|
+

|
|
55
|
+
|
|
56
|
+
Generated test illustration; it demonstrates the colour transform rather than
|
|
57
|
+
evidence from a private campaign. A video is omitted because the grade is a
|
|
58
|
+
stateless per-frame colour operation with no temporal behaviour.
|
|
59
|
+
|
|
25
60
|
## Per-element feed and clean overlay routing
|
|
26
61
|
|
|
27
62
|
FrameRudiments supports an optional project-level `visualiser_routing` object.
|
|
63
|
+
|
|
64
|
+
## Source-timed projectM preset changes
|
|
65
|
+
|
|
66
|
+
A projectM stage may retain one feedback instance while changing presets at
|
|
67
|
+
canonical source-audio times. The native projectM soft transition blends the
|
|
68
|
+
old and new preset; no frame is skipped and the existing PCM/output clock stays
|
|
69
|
+
authoritative.
|
|
70
|
+
|
|
71
|
+
```json
|
|
72
|
+
{
|
|
73
|
+
"id": "section-visualiser",
|
|
74
|
+
"engine": "projectm",
|
|
75
|
+
"preset": "feedback",
|
|
76
|
+
"preset_timeline": [
|
|
77
|
+
{"at_source_seconds": 62.46, "preset": "kaleidoscope", "transition_seconds": 0.42},
|
|
78
|
+
{"at_source_seconds": 128.60, "preset": "julia", "transition_seconds": 0.42}
|
|
79
|
+
]
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Times are measured in the unchanged source recording. Entries must be strictly
|
|
84
|
+
increasing. Built-in names retain their package hashes; external `.milk` files
|
|
85
|
+
still require `trusted_preset: true` on the individual timeline entry. Timeline
|
|
86
|
+
switches currently use projectM because its pinned native API provides an
|
|
87
|
+
auditable soft-cut transition on the same renderer instance. The manifest
|
|
88
|
+
records the scheduled boundary, actual frame/source time, preset hash and blend
|
|
89
|
+
duration.
|
|
28
90
|
It controls the **input RGBA texture** entering the first Butterchurn or
|
|
29
91
|
projectM stage and independently decides which original layers are composited
|
|
30
92
|
cleanly above the **last** visualiser result. The subsequent stages remain
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "framerudiments"
|
|
7
|
-
version = "0.4.
|
|
7
|
+
version = "0.4.6"
|
|
8
8
|
description = "Deterministic Shorts and lyric visualisers using DanceRudiments and StemLab evidence."
|
|
9
9
|
requires-python = ">=3.11"
|
|
10
10
|
readme = "README.md"
|
|
@@ -33,6 +33,8 @@ class FrameContext:
|
|
|
33
33
|
lyric: dict | None
|
|
34
34
|
cue: dict | None
|
|
35
35
|
word: dict | None = None
|
|
36
|
+
lyric_next: dict | None = None
|
|
37
|
+
lyric_next_beat: float | None = None
|
|
36
38
|
|
|
37
39
|
|
|
38
40
|
class TextBlock:
|
|
@@ -413,6 +415,7 @@ class Compositor:
|
|
|
413
415
|
def context(self, frame):
|
|
414
416
|
p = self.project
|
|
415
417
|
lyric = self.lyrics.at(frame)
|
|
418
|
+
lyric_next = self.lyrics.next_effective(frame)
|
|
416
419
|
word = self.words.at(frame, lyric if p.lyrics else None) if self.words else None
|
|
417
420
|
return FrameContext(
|
|
418
421
|
frame,
|
|
@@ -422,6 +425,8 @@ class Compositor:
|
|
|
422
425
|
lyric,
|
|
423
426
|
self.cues.at(frame),
|
|
424
427
|
word,
|
|
428
|
+
lyric_next,
|
|
429
|
+
p.continuous_beat(lyric_next["frame"]) if lyric_next is not None else None,
|
|
425
430
|
)
|
|
426
431
|
|
|
427
432
|
def _caption_draw(self, image, text, ctx):
|
|
@@ -8,6 +8,20 @@ import shutil
|
|
|
8
8
|
import subprocess
|
|
9
9
|
|
|
10
10
|
|
|
11
|
+
X264_PRESETS = (
|
|
12
|
+
"ultrafast",
|
|
13
|
+
"superfast",
|
|
14
|
+
"veryfast",
|
|
15
|
+
"faster",
|
|
16
|
+
"fast",
|
|
17
|
+
"medium",
|
|
18
|
+
"slow",
|
|
19
|
+
"slower",
|
|
20
|
+
"veryslow",
|
|
21
|
+
"placebo",
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
|
|
11
25
|
def binaries(ffmpeg=None, ffprobe=None):
|
|
12
26
|
ffmpeg = ffmpeg or os.environ.get("FRAMERUDIMENTS_FFMPEG") or shutil.which("ffmpeg")
|
|
13
27
|
if not ffmpeg:
|
|
@@ -56,7 +70,7 @@ def audio_filters(project):
|
|
|
56
70
|
|
|
57
71
|
|
|
58
72
|
def command(project, output, ffmpeg, *, preset="fast", crf=20):
|
|
59
|
-
if preset not in
|
|
73
|
+
if preset not in X264_PRESETS:
|
|
60
74
|
raise ValueError("Unsupported encoder preset")
|
|
61
75
|
if not isinstance(crf, int) or not 0 <= crf <= 51:
|
|
62
76
|
raise ValueError("CRF must be an integer in [0,51]")
|
|
@@ -107,6 +107,8 @@ class WordIndicator:
|
|
|
107
107
|
"offset_x",
|
|
108
108
|
"offset_y",
|
|
109
109
|
"travel_frames",
|
|
110
|
+
"travel_mode",
|
|
111
|
+
"travel_arc",
|
|
110
112
|
"bounce",
|
|
111
113
|
"rotation",
|
|
112
114
|
"particles",
|
|
@@ -142,6 +144,25 @@ class WordIndicator:
|
|
|
142
144
|
self.travel = integer(
|
|
143
145
|
config.get("travel_frames", 8), "indicator transition frames", maximum=120 * time_factor
|
|
144
146
|
)
|
|
147
|
+
self.travel_mode = config.get("travel_mode", "after_hit")
|
|
148
|
+
if self.travel_mode not in {"after_hit", "between_words"}:
|
|
149
|
+
raise ValueError("Indicator travel_mode must be after_hit or between_words")
|
|
150
|
+
self.travel_arc = number(
|
|
151
|
+
config.get("travel_arc", 0), "indicator travel arc", minimum=0, maximum=250
|
|
152
|
+
)
|
|
153
|
+
self._next_words = {}
|
|
154
|
+
timed_words = [
|
|
155
|
+
row for row in project.words if row.get("text") and row.get("word_index") is not None
|
|
156
|
+
]
|
|
157
|
+
for current, following in zip(timed_words, timed_words[1:]):
|
|
158
|
+
if (
|
|
159
|
+
current.get("cycle") == following.get("cycle")
|
|
160
|
+
and current.get("line_frame") == following.get("line_frame")
|
|
161
|
+
and current.get("line_order") == following.get("line_order")
|
|
162
|
+
and following["frame"] > current["frame"]
|
|
163
|
+
):
|
|
164
|
+
key = (current["frame"], current.get("source_order"), current.get("cycle"))
|
|
165
|
+
self._next_words[key] = following
|
|
145
166
|
self.style = dict(
|
|
146
167
|
indicator_size=number(config.get("size", 18), "indicator size", minimum=2, maximum=128),
|
|
147
168
|
indicator_glow=number(config.get("glow", 4), "indicator glow", minimum=0, maximum=24),
|
|
@@ -282,7 +303,13 @@ class WordIndicator:
|
|
|
282
303
|
font=self.font_ref,
|
|
283
304
|
glyph=self.glyph,
|
|
284
305
|
mode=self.particle_mode,
|
|
285
|
-
source_clock="encoded words/cues + native bounce axis",
|
|
306
|
+
source_clock="encoded words/cues + optional native bounce axis",
|
|
307
|
+
travel={
|
|
308
|
+
"mode": self.travel_mode,
|
|
309
|
+
"frames": self.travel,
|
|
310
|
+
"arc_pixels": self.travel_arc,
|
|
311
|
+
"landing_authority": "next encoded word frame" if self.travel_mode == "between_words" else None,
|
|
312
|
+
},
|
|
286
313
|
bounce=self.bounce.evidence if self.bounce else None,
|
|
287
314
|
modulations=self.modulations.evidence,
|
|
288
315
|
cue_file=self.cue_ref,
|
|
@@ -323,7 +350,21 @@ class WordIndicator:
|
|
|
323
350
|
if not 0 <= i < len(positions):
|
|
324
351
|
return None
|
|
325
352
|
x, y = positions[i]
|
|
326
|
-
|
|
353
|
+
arc = 0.0
|
|
354
|
+
if self.travel_mode == "between_words" and self.travel:
|
|
355
|
+
key = (word["frame"], word.get("source_order"), word.get("cycle"))
|
|
356
|
+
following = self._next_words.get(key)
|
|
357
|
+
if following is not None:
|
|
358
|
+
target_index = following["word_index"]
|
|
359
|
+
start = max(word["frame"], following["frame"] - self.travel)
|
|
360
|
+
if frame >= start and 0 <= target_index < len(positions):
|
|
361
|
+
u = _unit((frame - start) / max(1, following["frame"] - start))
|
|
362
|
+
smooth = u * u * (3 - 2 * u)
|
|
363
|
+
target = positions[target_index]
|
|
364
|
+
x = x + (target[0] - x) * smooth
|
|
365
|
+
y = y + (target[1] - y) * smooth
|
|
366
|
+
arc = math.sin(math.pi * u) * self.travel_arc
|
|
367
|
+
elif self.travel and i:
|
|
327
368
|
u = _unit((frame - word["frame"]) / self.travel)
|
|
328
369
|
smooth = u * u * (3 - 2 * u)
|
|
329
370
|
before = positions[i - 1]
|
|
@@ -337,6 +378,7 @@ class WordIndicator:
|
|
|
337
378
|
y -= h / 2 + style["offset_y"]
|
|
338
379
|
else:
|
|
339
380
|
y += self.block.maximum * 0.5
|
|
381
|
+
y -= arc
|
|
340
382
|
if self.bounce:
|
|
341
383
|
values = self.bounce.sample(ctx.source_seconds, continuous_beat=ctx.beat)
|
|
342
384
|
y -= values[self.bounce_axis] * style["bounce_amplitude"]
|
|
@@ -543,5 +585,7 @@ class WordIndicator:
|
|
|
543
585
|
particles_active=active,
|
|
544
586
|
cue_trigger=ctx.frame in self._events["cue"],
|
|
545
587
|
word_trigger=ctx.frame in self._events["word"],
|
|
588
|
+
travel_mode=self.travel_mode,
|
|
589
|
+
landed=ctx.frame == ctx.word["frame"],
|
|
546
590
|
)
|
|
547
591
|
return layer.getchannel("A").getbbox()
|
|
@@ -169,6 +169,7 @@ class ProjectMBackend:
|
|
|
169
169
|
"set_preset_locked": (None, [h, C.c_bool]),
|
|
170
170
|
"set_hard_cut_enabled": (None, [h, C.c_bool]),
|
|
171
171
|
"set_preset_start_clean": (None, [h, C.c_bool]),
|
|
172
|
+
"set_soft_cut_duration": (None, [h, C.c_double]),
|
|
172
173
|
"load_preset_file": (None, [h, C.c_char_p, C.c_bool]),
|
|
173
174
|
"set_preset_switch_failed_event_callback": (None, [h, C.c_void_p, h]),
|
|
174
175
|
"pcm_add_float": (None, [h, C.POINTER(C.c_float), C.c_uint, C.c_int]),
|
|
@@ -202,6 +203,24 @@ class ProjectMBackend:
|
|
|
202
203
|
def advance(self, image, context, pcm, *, capture=False):
|
|
203
204
|
return self.process(image, context, pcm, capture=capture)
|
|
204
205
|
|
|
206
|
+
def switch_preset(self, preset, transition_seconds):
|
|
207
|
+
"""Change preset on the existing feedback instance with projectM's soft transition."""
|
|
208
|
+
self.glfw.make_context_current(self.window)
|
|
209
|
+
self.lib.projectm_set_soft_cut_duration(self.handle, float(transition_seconds))
|
|
210
|
+
self.lib.projectm_load_preset_file(
|
|
211
|
+
self.handle,
|
|
212
|
+
os.fsencode(preset),
|
|
213
|
+
bool(transition_seconds),
|
|
214
|
+
)
|
|
215
|
+
self._check()
|
|
216
|
+
self.evidence.setdefault("preset_switches", []).append(
|
|
217
|
+
dict(
|
|
218
|
+
preset_path=str(Path(preset).resolve()),
|
|
219
|
+
preset_sha256=sha256(preset),
|
|
220
|
+
transition_seconds=float(transition_seconds),
|
|
221
|
+
)
|
|
222
|
+
)
|
|
223
|
+
|
|
205
224
|
def process(self, image, context, pcm, *, capture=True):
|
|
206
225
|
gl = self.gl
|
|
207
226
|
self.glfw.make_context_current(self.window)
|
|
@@ -111,6 +111,21 @@ class EventTrack:
|
|
|
111
111
|
index = bisect_right(self.frames, frame) - 1
|
|
112
112
|
return self.events[index] if index >= 0 else None
|
|
113
113
|
|
|
114
|
+
def next_effective(self, frame):
|
|
115
|
+
"""Return the rendered state at the next distinct event frame.
|
|
116
|
+
|
|
117
|
+
Several lyric ledgers deliberately write a blank clear followed by the
|
|
118
|
+
next line at the same frame. The last event at that frame is the state
|
|
119
|
+
a viewer actually sees, so a same-frame clear must not be mistaken for
|
|
120
|
+
a real lyric gap.
|
|
121
|
+
"""
|
|
122
|
+
index = bisect_right(self.frames, frame)
|
|
123
|
+
if index >= len(self.events):
|
|
124
|
+
return None
|
|
125
|
+
next_frame = self.events[index]["frame"]
|
|
126
|
+
end = bisect_right(self.frames, next_frame)
|
|
127
|
+
return self.events[end - 1]
|
|
128
|
+
|
|
114
129
|
def neighbours(self, frame):
|
|
115
130
|
index = bisect_right(self.frames, frame) - 1
|
|
116
131
|
current = self.events[index] if index >= 0 else None
|
|
@@ -8,7 +8,7 @@ async function initialise(config) {
|
|
|
8
8
|
const engine = window.butterchurn.default || window.butterchurn;
|
|
9
9
|
const viz = engine.createVisualizer(null, canvas, {
|
|
10
10
|
width: config.width, height: config.height, pixelRatio: 1, textureRatio: 1,
|
|
11
|
-
meshWidth:
|
|
11
|
+
meshWidth: config.mesh_width, meshHeight: config.mesh_height,
|
|
12
12
|
});
|
|
13
13
|
const r = viz.renderer, gl = r.gl;
|
|
14
14
|
for (const name of ['targetFrameBuffer', 'targetTexture', 'calcTimeAndFPS']) {
|
|
@@ -114,5 +114,6 @@ async function initialise(config) {
|
|
|
114
114
|
return {engine: 'butterchurn', version: '2.6.7', opengl: gl.getParameter(gl.VERSION),
|
|
115
115
|
renderer: info ? gl.getParameter(info.UNMASKED_RENDERER_WEBGL) : gl.getParameter(gl.RENDERER),
|
|
116
116
|
input: 'full composed RGBA injected into feedback before real MilkDrop warp/composite',
|
|
117
|
-
clock: 'pinned calcTimeAndFPS bridge: output frame / fps; fixed fps', seed: config.seed
|
|
117
|
+
clock: 'pinned calcTimeAndFPS bridge: output frame / fps; fixed fps', seed: config.seed,
|
|
118
|
+
mesh_width: config.mesh_width, mesh_height: config.mesh_height};
|
|
118
119
|
}
|
|
@@ -13,7 +13,7 @@ from PIL import Image
|
|
|
13
13
|
from .io import fields, number
|
|
14
14
|
|
|
15
15
|
MAX_TARGETS = 256
|
|
16
|
-
MODES = frozenset({"continuous", "first_pips", "never"})
|
|
16
|
+
MODES = frozenset({"continuous", "first_pips", "last_pips", "never"})
|
|
17
17
|
RETRIGGERS = frozenset({"reappearance", "once", "lyric", "cue"})
|
|
18
18
|
|
|
19
19
|
|
|
@@ -27,12 +27,12 @@ def _policy(config, label, inherited=None):
|
|
|
27
27
|
fields(feed, {"mode", "duration_pips"}, label + ".feed")
|
|
28
28
|
mode = feed.get("mode")
|
|
29
29
|
if mode not in MODES:
|
|
30
|
-
raise ValueError(f"{label}.feed.mode must be continuous, first_pips or never")
|
|
31
|
-
if mode
|
|
30
|
+
raise ValueError(f"{label}.feed.mode must be continuous, first_pips, last_pips or never")
|
|
31
|
+
if mode in {"first_pips", "last_pips"}:
|
|
32
32
|
duration = number(feed.get("duration_pips"), label + ".feed.duration_pips", minimum=0, maximum=65536)
|
|
33
33
|
feed = dict(mode=mode, duration_pips=duration)
|
|
34
34
|
elif "duration_pips" in feed:
|
|
35
|
-
raise ValueError(f"{label}.feed.duration_pips requires first_pips")
|
|
35
|
+
raise ValueError(f"{label}.feed.duration_pips requires first_pips or last_pips")
|
|
36
36
|
else:
|
|
37
37
|
feed = dict(mode=mode)
|
|
38
38
|
overlay = config.get("overlay", parent["overlay"])
|
|
@@ -41,6 +41,8 @@ def _policy(config, label, inherited=None):
|
|
|
41
41
|
retrigger = config.get("retrigger", parent["retrigger"])
|
|
42
42
|
if retrigger not in RETRIGGERS:
|
|
43
43
|
raise ValueError(f"{label}.retrigger must be reappearance, once, lyric or cue")
|
|
44
|
+
if feed["mode"] == "last_pips" and retrigger != "lyric":
|
|
45
|
+
raise ValueError(f"{label}.feed.mode last_pips requires retrigger=lyric")
|
|
44
46
|
return dict(feed=feed, overlay=overlay, retrigger=retrigger)
|
|
45
47
|
|
|
46
48
|
|
|
@@ -128,6 +130,20 @@ class VisualiserLayerRouting:
|
|
|
128
130
|
state["lyric"], state["cue"] = lyric, cue
|
|
129
131
|
mode = policy["feed"]["mode"]
|
|
130
132
|
elapsed = max(0.0, pip - state["onset"]) if state["onset"] is not None else None
|
|
133
|
+
lyric_next = getattr(context, "lyric_next", None)
|
|
134
|
+
lyric_next_beat = getattr(context, "lyric_next_beat", None)
|
|
135
|
+
remaining = (
|
|
136
|
+
max(0.0, float(lyric_next_beat) * 64.0 - pip)
|
|
137
|
+
if lyric_next_beat is not None and math.isfinite(float(lyric_next_beat))
|
|
138
|
+
else None
|
|
139
|
+
)
|
|
140
|
+
lyric_gap_exit = bool(
|
|
141
|
+
trigger == "lyric"
|
|
142
|
+
and getattr(context, "lyric", None)
|
|
143
|
+
and getattr(context, "lyric", {}).get("text")
|
|
144
|
+
and lyric_next is not None
|
|
145
|
+
and not lyric_next.get("text")
|
|
146
|
+
)
|
|
131
147
|
feeding = bool(
|
|
132
148
|
visible
|
|
133
149
|
and (
|
|
@@ -135,6 +151,10 @@ class VisualiserLayerRouting:
|
|
|
135
151
|
or mode == "first_pips"
|
|
136
152
|
and elapsed is not None
|
|
137
153
|
and elapsed < policy["feed"]["duration_pips"]
|
|
154
|
+
or mode == "last_pips"
|
|
155
|
+
and lyric_gap_exit
|
|
156
|
+
and remaining is not None
|
|
157
|
+
and 0.0 < remaining <= policy["feed"]["duration_pips"]
|
|
138
158
|
)
|
|
139
159
|
)
|
|
140
160
|
on_top = bool(visible and policy["overlay"])
|
|
@@ -149,6 +169,8 @@ class VisualiserLayerRouting:
|
|
|
149
169
|
overlay=on_top,
|
|
150
170
|
first_pip=state["onset"],
|
|
151
171
|
elapsed_pips=elapsed,
|
|
172
|
+
remaining_pips=remaining,
|
|
173
|
+
lyric_gap_exit=lyric_gap_exit,
|
|
152
174
|
policy=policy["feed"]["mode"],
|
|
153
175
|
)
|
|
154
176
|
self.next_frame += 1
|