framerudiments 0.4.7__tar.gz → 0.4.8__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.7/src/framerudiments.egg-info → framerudiments-0.4.8}/PKG-INFO +17 -1
- {framerudiments-0.4.7 → framerudiments-0.4.8}/README.md +16 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/RELEASE_NOTES.md +19 -0
- framerudiments-0.4.8/docs/media/visualiser-feedback-writeback.json +45 -0
- framerudiments-0.4.8/docs/media/visualiser-feedback-writeback.png +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/docs/project-format.md +3 -2
- {framerudiments-0.4.7 → framerudiments-0.4.8}/docs/visualisers.md +105 -0
- framerudiments-0.4.8/integration/build_feedback_contact_sheet.py +83 -0
- framerudiments-0.4.8/integration/verify_feedback_writeback.py +117 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/pyproject.toml +1 -1
- {framerudiments-0.4.7 → framerudiments-0.4.8}/src/framerudiments/__init__.py +1 -1
- {framerudiments-0.4.7 → framerudiments-0.4.8}/src/framerudiments/compositor.py +13 -2
- {framerudiments-0.4.7 → framerudiments-0.4.8}/src/framerudiments/projectm_backend.py +33 -16
- {framerudiments-0.4.7 → framerudiments-0.4.8}/src/framerudiments/render.py +16 -2
- {framerudiments-0.4.7 → framerudiments-0.4.8}/src/framerudiments/visualiser_assets/butterchurn-page.js +13 -7
- framerudiments-0.4.8/src/framerudiments/visualiser_compositing.py +146 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/src/framerudiments/visualisers.py +108 -3
- {framerudiments-0.4.7 → framerudiments-0.4.8/src/framerudiments.egg-info}/PKG-INFO +17 -1
- {framerudiments-0.4.7 → framerudiments-0.4.8}/src/framerudiments.egg-info/SOURCES.txt +6 -0
- framerudiments-0.4.8/tests/test_visualiser_compositing.py +215 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/tests/test_visualisers.py +29 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/AGENTS.md +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/LICENSE +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/MANIFEST.in +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/analysis-releases.json +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/docs/ECOSYSTEM.md +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/docs/FrameRudiments-PATCH_WORKFLOW.md +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/docs/annular-modulation.md +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/docs/authoring-workspace.md +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/docs/beat-alignment.md +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/docs/bounded-preflight.md +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/docs/controlled-audio-response.md +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/docs/expanded-effects.md +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/docs/extraction.md +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/docs/full-preset-analysis.md +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/docs/hybrid-preset-analysis.md +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/docs/keyframed-mattes.md +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/docs/media/beat-alignment-evidence.json +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/docs/media/beat-alignment.mp4 +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/docs/media/beat-alignment.png +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/docs/media/bounded-preflight.svg +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/docs/media/demo-transcript.md +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/docs/media/effects-pipeline.svg +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/docs/media/full-frame-comparison.png +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/docs/media/full-frame-demo.mp4 +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/docs/media/media-evidence.json +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/docs/media/natural-materials.mp4 +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/docs/media/natural-materials.png +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/docs/media/palette-grade-comparison.png +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/docs/media/preset-test-cards.png +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/docs/media/rangoli-transcript.md +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/docs/media/rangoli.mp4 +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/docs/media/rangoli.png +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/docs/media/test-card-inputs/colour-bars.png +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/docs/media/test-card-inputs/grid-rings.png +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/docs/media/test-card-inputs/text-card.png +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/docs/multi-stem-modulation.md +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/docs/music-reactive-3d.md +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/docs/natural-materials.md +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/docs/particle-smoke.md +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/docs/phase3-compositing.md +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/docs/preset-behaviour.md +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/docs/preset-profiling.md +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/docs/projectm-local-runtime.md +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/docs/quality-effects.md +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/docs/rangoli.md +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/docs/rich-lyric-effects.md +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/docs/rudiment-renderers.md +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/docs/scoped-post-effects.md +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/docs/solid-spatial-renderers.md +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/docs/spatial-renderers.md +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/docs/spatiotemporal-supersampling.md +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/docs/spectrum.md +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/docs/sprite-visualisations.md +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/docs/stem-sources-and-masks.md +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/docs/variable-bpm-word-subtitles.md +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/docs/visualisation-atlas.md +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/docs/visualisation-coverage-matrix.md +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/docs/visualiser-routing.md +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/docs/workflow-media.md +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/integration/test_fire_volume_gpu.py +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/integration/test_music_spatial_gpu.py +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/integration/test_preset_analysis.py +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/integration/test_released_preset_analysis.py +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/integration/test_visualisers_gpu.py +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/scripts/Run-ControlledPresetAnalysis.ps1 +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/scripts/Run-FullPresetAnalysis.ps1 +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/scripts/Run-HybridPresetAnalysis.ps1 +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/scripts/Run-PresetBehaviourAnalysis.ps1 +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/scripts/authoring_preview.py +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/scripts/bake_mantaflow_fire.py +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/scripts/build_sprite_assets.py +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/scripts/check_projectm_runtime.py +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/scripts/check_release_packages.py +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/scripts/controlled_preset_analysis.py +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/scripts/ensure_projectm_runtime.py +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/scripts/full_preset_analysis.py +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/scripts/generate_particle_gallery.py +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/scripts/generate_post_effect_gallery.py +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/scripts/generate_quality_gallery.py +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/scripts/generate_rich_lyric_gallery.py +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/scripts/generate_sprite_gallery.py +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/scripts/generate_supersampling_gallery.py +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/scripts/generate_timing_gallery.py +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/scripts/generate_visualisation_gallery.py +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/scripts/generate_visualiser_routing_gallery.py +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/scripts/install_analysis_releases.py +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/scripts/prepare_fire_atlas.py +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/scripts/prepare_pixelcue_taggers.py +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/scripts/preset_behaviour_pipeline.py +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/scripts/profile_visualisation_coverage.py +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/scripts/profile_visualisers.py +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/scripts/projectm_dll_diagnostics.py +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/scripts/render_natural_material_demo.py +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/scripts/repair_projectm_mingw.py +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/scripts/select_projectm_generator.py +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/scripts/validate_patch.py +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/setup.cfg +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/src/framerudiments/__main__.py +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/src/framerudiments/advanced_renderers.py +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/src/framerudiments/atlas.py +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/src/framerudiments/audio_probes.py +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/src/framerudiments/beat_alignment.py +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/src/framerudiments/beat_timeline.py +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/src/framerudiments/choreography.py +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/src/framerudiments/circular_renderers.py +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/src/framerudiments/cli.py +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/src/framerudiments/controlled_profiling.py +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/src/framerudiments/demo.py +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/src/framerudiments/effect_catalogue.py +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/src/framerudiments/effect_renderers.py +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/src/framerudiments/effects.py +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/src/framerudiments/encoding.py +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/src/framerudiments/fire_assets/mantaflow/NOTICE.md +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/src/framerudiments/fire_assets/mantaflow/atlas.json +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/src/framerudiments/fire_assets/mantaflow/flames.png +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/src/framerudiments/fire_assets/marburg/NOTICE.md +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/src/framerudiments/fire_assets/marburg/atlas.json +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/src/framerudiments/fire_assets/marburg/flames.png +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/src/framerudiments/fire_assets.py +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/src/framerudiments/fire_fluid.py +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/src/framerudiments/fire_renderer.py +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/src/framerudiments/fire_volume.py +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/src/framerudiments/flight.py +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/src/framerudiments/gpu_spatial_backend.py +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/src/framerudiments/io.py +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/src/framerudiments/keyframes.py +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/src/framerudiments/layer_animation.py +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/src/framerudiments/layer_displacement.py +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/src/framerudiments/layer_masks.py +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/src/framerudiments/lyric_indicator.py +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/src/framerudiments/modulation.py +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/src/framerudiments/motion.py +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/src/framerudiments/music_spatial_renderers.py +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/src/framerudiments/natural_materials.py +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/src/framerudiments/output_quality.py +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/src/framerudiments/particle_engine.py +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/src/framerudiments/post_effects.py +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/src/framerudiments/preset_library.py +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/src/framerudiments/preset_profiling.py +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/src/framerudiments/profile_analysis.py +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/src/framerudiments/project.py +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/src/framerudiments/quality_effects.py +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/src/framerudiments/rangoli_renderer.py +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/src/framerudiments/rich_subtitles.py +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/src/framerudiments/rudiment_renderers.py +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/src/framerudiments/scoped_effects.py +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/src/framerudiments/solid_spatial_renderers.py +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/src/framerudiments/spatial_renderers.py +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/src/framerudiments/spectrum.py +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/src/framerudiments/sprite_assets/arcade.png +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/src/framerudiments/sprite_assets/crystal.png +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/src/framerudiments/sprite_assets/fire.png +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/src/framerudiments/sprite_assets/generated-neon.png +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/src/framerudiments/sprite_assets/glass.png +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/src/framerudiments/sprite_assets/hud.png +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/src/framerudiments/sprite_assets/ice.png +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/src/framerudiments/sprite_assets/lcd-amber.png +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/src/framerudiments/sprite_assets/lcd-green.png +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/src/framerudiments/sprite_assets/lcd.png +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/src/framerudiments/sprite_assets/led.png +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/src/framerudiments/sprite_assets/manifest.json +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/src/framerudiments/sprite_assets/metal.png +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/src/framerudiments/sprite_assets/mono.png +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/src/framerudiments/sprite_assets/neon.png +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/src/framerudiments/sprite_assets/pixel.png +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/src/framerudiments/sprite_assets/plasma.png +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/src/framerudiments/sprite_assets/vfd.png +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/src/framerudiments/sprite_catalogue.py +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/src/framerudiments/sprite_renderers.py +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/src/framerudiments/stem_blend.py +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/src/framerudiments/stemlab.py +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/src/framerudiments/subtitles.py +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/src/framerudiments/super_sampling.py +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/src/framerudiments/terrain_field.py +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/src/framerudiments/timeline.py +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/src/framerudiments/visual_style.py +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/src/framerudiments/visualisation_catalogue.py +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/src/framerudiments/visualiser_assets/butterchurn-worker.mjs +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/src/framerudiments/visualiser_assets/feedback.json +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/src/framerudiments/visualiser_assets/feedback.milk +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/src/framerudiments/visualiser_assets/julia.json +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/src/framerudiments/visualiser_assets/julia.milk +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/src/framerudiments/visualiser_assets/kaleidoscope.json +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/src/framerudiments/visualiser_assets/kaleidoscope.milk +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/src/framerudiments/visualiser_assets/package-lock.json +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/src/framerudiments/visualiser_assets/package.json +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/src/framerudiments/visualiser_assets/rangoli-background.milk +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/src/framerudiments/visualiser_routing.py +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/src/framerudiments/visualiser_setup.py +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/src/framerudiments/word_timing.py +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/src/framerudiments.egg-info/dependency_links.txt +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/src/framerudiments.egg-info/entry_points.txt +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/src/framerudiments.egg-info/requires.txt +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/src/framerudiments.egg-info/top_level.txt +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/tests/conftest.py +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/tests/test_adapters.py +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/tests/test_advanced_renderers.py +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/tests/test_authoring_preview.py +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/tests/test_beat_alignment.py +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/tests/test_behaviour_pipeline.py +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/tests/test_checkpoint_batches.py +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/tests/test_controlled_audio.py +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/tests/test_coverage_matrix.py +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/tests/test_default_antialias.py +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/tests/test_effect_batch.py +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/tests/test_encoding.py +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/tests/test_extracted_helpers.py +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/tests/test_fire_methods.py +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/tests/test_flight.py +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/tests/test_full_preset_analysis.py +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/tests/test_gpu_ci_isolation.py +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/tests/test_hybrid_launcher.py +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/tests/test_keyframes.py +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/tests/test_layer_masks.py +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/tests/test_lyrics.py +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/tests/test_music_spatial_pipeline.py +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/tests/test_music_spatial_renderers.py +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/tests/test_natural_materials.py +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/tests/test_particle_engine.py +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/tests/test_patch_validation.py +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/tests/test_phase3_animation.py +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/tests/test_phase3_gpu_injection.py +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/tests/test_pipeline.py +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/tests/test_preset_profiling.py +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/tests/test_projectm_dll_diagnostics.py +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/tests/test_projectm_generator.py +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/tests/test_projectm_runtime_preflight.py +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/tests/test_pytest_temp_isolation.py +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/tests/test_quality_effects.py +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/tests/test_rangoli_renderer.py +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/tests/test_release_metadata.py +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/tests/test_repair_projectm_mingw.py +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/tests/test_rich_subtitles.py +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/tests/test_rudiment_renderers.py +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/tests/test_scoped_effects.py +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/tests/test_solid_spatial_integration.py +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/tests/test_solid_spatial_renderers.py +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/tests/test_spatial_renderers.py +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/tests/test_spectrum.py +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/tests/test_sprite_renderers.py +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/tests/test_stem_blend.py +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/tests/test_stem_source_integration.py +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/tests/test_supersampling.py +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/tests/test_timeline.py +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/tests/test_variable_bpm_words.py +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/tests/test_visualisation_modulation.py +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/tests/test_visualiser_routing.py +0 -0
- {framerudiments-0.4.7 → framerudiments-0.4.8}/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.8
|
|
4
4
|
Summary: Deterministic Shorts and lyric visualisers using DanceRudiments and StemLab evidence.
|
|
5
5
|
Author: Kieran Simkin
|
|
6
6
|
License-Expression: MIT
|
|
@@ -358,6 +358,22 @@ a browser or projectM. Feedback is sequential, and GPU output can vary by driver
|
|
|
358
358
|
Inspect processed/encoded glyphs and safe zones; pre-effect text bounds cannot
|
|
359
359
|
prove final legibility.
|
|
360
360
|
|
|
361
|
+
Each visualiser stage can also be chroma-keyed, constrained by a named animated
|
|
362
|
+
alpha/luminance track matte, faded, or use all three operations together. The
|
|
363
|
+
processed stage is composited over its own input before the next engine, so
|
|
364
|
+
projectM and Butterchurn can occupy different regions while retaining their
|
|
365
|
+
continuous source-time feedback. See [stage-level keying and matting](docs/visualisers.md#stage-level-chroma-key-and-track-mattes).
|
|
366
|
+
|
|
367
|
+
The same stage can apply the complete FrameRudiments visual-effect pipeline at
|
|
368
|
+
its source input, directly to the retained Butterchurn/projectM feedback state,
|
|
369
|
+
or to display output. These hooks are separate and evidence-recorded; a
|
|
370
|
+
feedback hook is written back for consumption by the next frame.
|
|
371
|
+
|
|
372
|
+
The [real-engine feedback smoke evidence](docs/media/visualiser-feedback-writeback.png)
|
|
373
|
+
shows the control frame, injected retained state and measured pixel difference
|
|
374
|
+
for both pinned engines. It uses synthetic imagery and is evidence of the hook,
|
|
375
|
+
not a claim about a campaign render.
|
|
376
|
+
|
|
361
377
|
Version 0.1.4 preserves portrait geometry in pinned projectM legacy presets through a recorded square overscan/crop mapping; the upstream engine remains unmodified. Neutral portrait/landscape/square mapping is tested for both backends.
|
|
362
378
|
|
|
363
379
|

|
|
@@ -327,6 +327,22 @@ a browser or projectM. Feedback is sequential, and GPU output can vary by driver
|
|
|
327
327
|
Inspect processed/encoded glyphs and safe zones; pre-effect text bounds cannot
|
|
328
328
|
prove final legibility.
|
|
329
329
|
|
|
330
|
+
Each visualiser stage can also be chroma-keyed, constrained by a named animated
|
|
331
|
+
alpha/luminance track matte, faded, or use all three operations together. The
|
|
332
|
+
processed stage is composited over its own input before the next engine, so
|
|
333
|
+
projectM and Butterchurn can occupy different regions while retaining their
|
|
334
|
+
continuous source-time feedback. See [stage-level keying and matting](docs/visualisers.md#stage-level-chroma-key-and-track-mattes).
|
|
335
|
+
|
|
336
|
+
The same stage can apply the complete FrameRudiments visual-effect pipeline at
|
|
337
|
+
its source input, directly to the retained Butterchurn/projectM feedback state,
|
|
338
|
+
or to display output. These hooks are separate and evidence-recorded; a
|
|
339
|
+
feedback hook is written back for consumption by the next frame.
|
|
340
|
+
|
|
341
|
+
The [real-engine feedback smoke evidence](docs/media/visualiser-feedback-writeback.png)
|
|
342
|
+
shows the control frame, injected retained state and measured pixel difference
|
|
343
|
+
for both pinned engines. It uses synthetic imagery and is evidence of the hook,
|
|
344
|
+
not a claim about a campaign render.
|
|
345
|
+
|
|
330
346
|
Version 0.1.4 preserves portrait geometry in pinned projectM legacy presets through a recorded square overscan/crop mapping; the upstream engine remains unmodified. Neutral portrait/landscape/square mapping is tested for both backends.
|
|
331
347
|
|
|
332
348
|

|
|
@@ -1,3 +1,22 @@
|
|
|
1
|
+
# FrameRudiments 0.4.8
|
|
2
|
+
|
|
3
|
+
- Butterchurn and projectM stages now support deterministic post-stage chroma
|
|
4
|
+
keying, edge smoothing and bounded despill.
|
|
5
|
+
- The complete VisualStyle effect set can run on a stage's source input,
|
|
6
|
+
retained feedback state, or display output. Retained-feedback treatments are
|
|
7
|
+
written back through Butterchurn's pinned target framebuffer and projectM's
|
|
8
|
+
pinned 4.2 texture-burn API before the next frame.
|
|
9
|
+
- A stage can use any named isolated compositor element as an alpha or
|
|
10
|
+
luminance track matte, with inversion and independent matte/stage opacity.
|
|
11
|
+
- Keying and matting compose in a documented order over the image that entered
|
|
12
|
+
each stage; later engines receive the already contained result while clean
|
|
13
|
+
routed text remains above the final chain.
|
|
14
|
+
- Focused pixel tests cover hard/soft keys, combined key-plus-matte output,
|
|
15
|
+
luminance/alpha multiplication and two-engine stage order.
|
|
16
|
+
- Pinned real-engine smoke evidence is included at
|
|
17
|
+
`docs/media/visualiser-feedback-writeback.png`, with source hashes in the
|
|
18
|
+
adjacent JSON receipt.
|
|
19
|
+
|
|
1
20
|
# FrameRudiments 0.4.7
|
|
2
21
|
|
|
3
22
|
- `preflight --max-frames N` composes a declared sequential prefix for expensive
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schema": "framerudiments.feedback-contact-sheet.v1",
|
|
3
|
+
"synthetic_input": true,
|
|
4
|
+
"purpose": "Visual evidence of retained-feedback writeback in pinned real engines.",
|
|
5
|
+
"output": "docs\\media\\visualiser-feedback-writeback.png",
|
|
6
|
+
"output_sha256": "5d35e7555196c539bf669616405f759754472661c7838e2cfe92d74c74b323d5",
|
|
7
|
+
"sources": [
|
|
8
|
+
{
|
|
9
|
+
"engine": "Butterchurn",
|
|
10
|
+
"state": "control",
|
|
11
|
+
"file": "butterchurn-control.png",
|
|
12
|
+
"sha256": "c0210c81e18d3626fddcbae69151c11246879123fef5c27a7b8647c82ffb3225"
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"engine": "Butterchurn",
|
|
16
|
+
"state": "feedback writeback",
|
|
17
|
+
"file": "butterchurn-feedback-hook.png",
|
|
18
|
+
"sha256": "406a7a828b49775a506507b59097588e0bc05d2f63ca28de571df211732f8a2f"
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"engine": "Butterchurn",
|
|
22
|
+
"state": "pixel difference",
|
|
23
|
+
"file": "butterchurn-difference.png",
|
|
24
|
+
"sha256": "9ac9efc47ca3eb0dff21af46aaa5e8f7f7879bb4c5f004c64ca1fe2111a6d08d"
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"engine": "projectM",
|
|
28
|
+
"state": "control",
|
|
29
|
+
"file": "projectm-control.png",
|
|
30
|
+
"sha256": "8ff0c598bde30f1c53f7037a46142c47e9e87a7d68cc66e87871d973d19cd74f"
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
"engine": "projectM",
|
|
34
|
+
"state": "feedback writeback",
|
|
35
|
+
"file": "projectm-feedback-hook.png",
|
|
36
|
+
"sha256": "3f8c01f0e568dfe61e691ebddd7a9c714bfc515d59613a6f14bae126950c4b08"
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
"engine": "projectM",
|
|
40
|
+
"state": "pixel difference",
|
|
41
|
+
"file": "projectm-difference.png",
|
|
42
|
+
"sha256": "2eac385f46c092d80e71224567f012b108505cc79c92c5a436a033af7e0dd9fc"
|
|
43
|
+
}
|
|
44
|
+
]
|
|
45
|
+
}
|
|
Binary file
|
|
@@ -23,7 +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`
|
|
26
|
+
| `effects` | Ordered Butterchurn/projectM stages. Butterchurn may set a denser `mesh_width`/`mesh_height`; any stage may set `input_effects`, retained-loop `feedback_effects`, `output_effects`, a multi-stop `output_palette_grade`, and a stage-level `composite` containing chroma key, named alpha/luminance matte and opacity controls. 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). |
|
|
27
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. |
|
|
28
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. |
|
|
29
29
|
|
|
@@ -156,7 +156,8 @@ see [full schema and examples](rich-lyric-effects.md).
|
|
|
156
156
|
## Optional full-frame effects
|
|
157
157
|
|
|
158
158
|
The top-level `effects` list is additive to v1. See [visualisers.md](visualisers.md)
|
|
159
|
-
for the `id`, `engine`, `preset`, `input_mix`, `seed
|
|
159
|
+
for the `id`, `engine`, `preset`, `input_mix`, `seed`, `trusted_preset` and
|
|
160
|
+
stage-level `composite` fields,
|
|
160
161
|
runtime setup and sequential feedback contract. Both engines process all existing
|
|
161
162
|
layers, including native moving sprites/atlases, captions and lyric art, before
|
|
162
163
|
encoding. Existing projects without `effects` keep the original composition path.
|
|
@@ -57,6 +57,111 @@ Generated test illustration; it demonstrates the colour transform rather than
|
|
|
57
57
|
evidence from a private campaign. A video is omitted because the grade is a
|
|
58
58
|
stateless per-frame colour operation with no temporal behaviour.
|
|
59
59
|
|
|
60
|
+
## Stage-level chroma key and track mattes
|
|
61
|
+
|
|
62
|
+
### Input, retained-feedback and output effect hooks
|
|
63
|
+
|
|
64
|
+
Every stage accepts three ordered arrays using the same `EffectStage` schema as
|
|
65
|
+
`post_effects.frame`: `input_effects`, `feedback_effects` and
|
|
66
|
+
`output_effects`. They support the complete validated `VisualStyle` set and
|
|
67
|
+
native DanceRudiment modulations.
|
|
68
|
+
|
|
69
|
+
```json
|
|
70
|
+
{
|
|
71
|
+
"id": "cloud-feedback",
|
|
72
|
+
"engine": "butterchurn",
|
|
73
|
+
"preset": "feedback",
|
|
74
|
+
"input_effects": [
|
|
75
|
+
{"id": "source-grade", "visual": {"saturation": 0.7, "contrast": 1.1}}
|
|
76
|
+
],
|
|
77
|
+
"feedback_effects": [
|
|
78
|
+
{"id": "memory-drift", "visual": {"hue_shift": 4, "blur": 0.6}}
|
|
79
|
+
],
|
|
80
|
+
"output_effects": [
|
|
81
|
+
{"id": "display-finish", "visual": {"grain": 0.03, "vignette": 0.2}}
|
|
82
|
+
]
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
- `input_effects` changes the source texture before it is injected into that
|
|
87
|
+
engine's warp/composite pass. It therefore participates in feedback.
|
|
88
|
+
- `feedback_effects` processes the engine frame and writes those pixels back
|
|
89
|
+
into the engine's retained feedback target before the next frame consumes
|
|
90
|
+
it. Butterchurn writes to its pinned `targetFrameBuffer`; projectM uses the
|
|
91
|
+
pinned 4.2 `projectm_opengl_burn_texture` API. This is an actual loop-state
|
|
92
|
+
mutation and requires one GPU readback/upload per affected stage and frame.
|
|
93
|
+
- `output_effects` changes only the displayed stage result. It does not alter
|
|
94
|
+
retained feedback unless the same treatment is also declared under
|
|
95
|
+
`feedback_effects`.
|
|
96
|
+
|
|
97
|
+
The exact order is stage input injection → `input_effects` → engine render →
|
|
98
|
+
`feedback_effects` branch/writeback → legacy `output_palette_grade` →
|
|
99
|
+
`output_effects` → chroma key → track matte → stage opacity/alpha-over. The
|
|
100
|
+
next engine receives that composited display result. Clean routing overlays
|
|
101
|
+
remain after the final engine.
|
|
102
|
+
|
|
103
|
+

|
|
104
|
+
|
|
105
|
+
*Real-engine GPU smoke evidence from the pinned Butterchurn and projectM
|
|
106
|
+
runtimes. The magenta card is deliberately written into each engine's retained
|
|
107
|
+
feedback target; the following frame changes before ordinary output processing.
|
|
108
|
+
Inputs are synthetic and the enlarged nearest-neighbour pixels make the
|
|
109
|
+
difference inspectable. Source hashes are retained in the adjacent
|
|
110
|
+
`visualiser-feedback-writeback.json` receipt.*
|
|
111
|
+
|
|
112
|
+
Each Butterchurn or projectM stage may declare `composite`. FrameRudiments
|
|
113
|
+
renders the visualiser normally, optionally removes a keyed colour, multiplies
|
|
114
|
+
the remaining alpha by a named isolated compositor layer, then composites that
|
|
115
|
+
result over the image that entered the stage. Both operations can be combined:
|
|
116
|
+
|
|
117
|
+
```json
|
|
118
|
+
{
|
|
119
|
+
"id": "coastal-light",
|
|
120
|
+
"engine": "projectm",
|
|
121
|
+
"preset": "julia",
|
|
122
|
+
"composite": {
|
|
123
|
+
"opacity": 0.82,
|
|
124
|
+
"chroma_key": {
|
|
125
|
+
"colour": "#00ff00",
|
|
126
|
+
"similarity": 0.10,
|
|
127
|
+
"smoothness": 0.08,
|
|
128
|
+
"despill": 0.35
|
|
129
|
+
},
|
|
130
|
+
"matte": {
|
|
131
|
+
"source": "layer:moving-clouds",
|
|
132
|
+
"mode": "luminance",
|
|
133
|
+
"invert": false,
|
|
134
|
+
"opacity": 0.9
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
`similarity` is the fully removed maximum per-channel RGB distance from the
|
|
141
|
+
key colour, normalised to 0..1. `smoothness` is the following soft transition
|
|
142
|
+
to fully opaque pixels. A zero smoothness gives a hard key. `despill` blends
|
|
143
|
+
near-key edge pixels toward their luminance without altering distant colours.
|
|
144
|
+
|
|
145
|
+
The matte `source` names an actual isolated compositor element such as
|
|
146
|
+
`layer:moving-clouds`, `background`, `scene`, `caption` or `lyric-art:0`.
|
|
147
|
+
Unknown names fail during composition setup. `alpha` uses its rendered alpha;
|
|
148
|
+
`luminance` multiplies that alpha by greyscale RGB. `invert`, matte `opacity`
|
|
149
|
+
and stage `opacity` are applied after keying. An invisible or gated matte
|
|
150
|
+
source produces an empty contribution for that frame.
|
|
151
|
+
|
|
152
|
+
Compositing happens independently after every configured stage. The next
|
|
153
|
+
visualiser receives the already keyed/matted result, so two engines can occupy
|
|
154
|
+
different spatial regions without a release-owned frame loop. The visualiser
|
|
155
|
+
still advances across the full sequential programme even when its visible
|
|
156
|
+
matte is empty; its feedback state and PCM clock therefore remain continuous.
|
|
157
|
+
Clean `visualiser_routing` overlays are drawn after the last stage as before.
|
|
158
|
+
|
|
159
|
+
Keying is colour removal, not evidence that a preset was authored for a green
|
|
160
|
+
screen. Choose a key colour demonstrated to be isolated in inspected output,
|
|
161
|
+
and inspect edge transparency at delivery size. Track mattes preserve the
|
|
162
|
+
provider's rendered motion and post effects; they do not infer a mask from a
|
|
163
|
+
filename or visual role.
|
|
164
|
+
|
|
60
165
|
## Per-element feed and clean overlay routing
|
|
61
166
|
|
|
62
167
|
FrameRudiments supports an optional project-level `visualiser_routing` object.
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"""Build a labelled documentation image from real-engine feedback smoke outputs."""
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import hashlib
|
|
5
|
+
import json
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
from PIL import Image, ImageDraw, ImageFont
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def file_hash(path):
|
|
12
|
+
return hashlib.sha256(path.read_bytes()).hexdigest()
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def main():
|
|
16
|
+
parser = argparse.ArgumentParser()
|
|
17
|
+
parser.add_argument("--butterchurn-dir", type=Path, required=True)
|
|
18
|
+
parser.add_argument("--projectm-dir", type=Path, required=True)
|
|
19
|
+
parser.add_argument("--output", type=Path, required=True)
|
|
20
|
+
args = parser.parse_args()
|
|
21
|
+
|
|
22
|
+
entries = []
|
|
23
|
+
for engine, folder in (
|
|
24
|
+
("Butterchurn", args.butterchurn_dir),
|
|
25
|
+
("projectM", args.projectm_dir),
|
|
26
|
+
):
|
|
27
|
+
for state, filename in (
|
|
28
|
+
("control", f"{engine.lower()}-control.png"),
|
|
29
|
+
("feedback writeback", f"{engine.lower()}-feedback-hook.png"),
|
|
30
|
+
("pixel difference", f"{engine.lower()}-difference.png"),
|
|
31
|
+
):
|
|
32
|
+
path = folder / filename
|
|
33
|
+
entries.append((engine, state, path, Image.open(path).convert("RGB")))
|
|
34
|
+
|
|
35
|
+
scale = 3
|
|
36
|
+
tile = (96 * scale, 64 * scale)
|
|
37
|
+
margin, label_height, footer_height = 20, 42, 22
|
|
38
|
+
sheet = Image.new(
|
|
39
|
+
"RGB",
|
|
40
|
+
(
|
|
41
|
+
margin * 4 + tile[0] * 3,
|
|
42
|
+
margin * 3 + (label_height + tile[1] + footer_height) * 2,
|
|
43
|
+
),
|
|
44
|
+
"#0b1020",
|
|
45
|
+
)
|
|
46
|
+
draw = ImageDraw.Draw(sheet)
|
|
47
|
+
font = ImageFont.load_default(size=16)
|
|
48
|
+
small = ImageFont.load_default(size=13)
|
|
49
|
+
for index, (engine, state, path, image) in enumerate(entries):
|
|
50
|
+
row, column = divmod(index, 3)
|
|
51
|
+
x = margin + column * (tile[0] + margin)
|
|
52
|
+
y = margin + row * (tile[1] + label_height + footer_height + margin)
|
|
53
|
+
draw.text((x, y), f"{engine} - {state}", font=font, fill="#f4f0e8")
|
|
54
|
+
resized = image.resize(tile, Image.Resampling.NEAREST)
|
|
55
|
+
sheet.paste(resized, (x, y + label_height))
|
|
56
|
+
draw.rectangle((x, y + label_height, x + tile[0] - 1, y + label_height + tile[1] - 1), outline="#5ce1e6", width=2)
|
|
57
|
+
draw.text((x, y + label_height + tile[1] + 3), path.name, font=small, fill="#9aa8c7")
|
|
58
|
+
|
|
59
|
+
args.output.parent.mkdir(parents=True, exist_ok=True)
|
|
60
|
+
sheet.save(args.output, optimize=True)
|
|
61
|
+
receipt = {
|
|
62
|
+
"schema": "framerudiments.feedback-contact-sheet.v1",
|
|
63
|
+
"synthetic_input": True,
|
|
64
|
+
"purpose": "Visual evidence of retained-feedback writeback in pinned real engines.",
|
|
65
|
+
"output": str(args.output),
|
|
66
|
+
"output_sha256": file_hash(args.output),
|
|
67
|
+
"sources": [
|
|
68
|
+
{
|
|
69
|
+
"engine": engine,
|
|
70
|
+
"state": state,
|
|
71
|
+
"file": path.name,
|
|
72
|
+
"sha256": file_hash(path),
|
|
73
|
+
}
|
|
74
|
+
for engine, state, path, _ in entries
|
|
75
|
+
],
|
|
76
|
+
}
|
|
77
|
+
args.output.with_suffix(".json").write_text(
|
|
78
|
+
json.dumps(receipt, indent=2) + "\n", encoding="utf-8"
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
if __name__ == "__main__":
|
|
83
|
+
main()
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
"""Run opt-in real-engine feedback-writeback smoke checks.
|
|
2
|
+
|
|
3
|
+
This script needs the documented Butterchurn and projectM environment variables.
|
|
4
|
+
It writes tiny synthetic PNG evidence and a JSON receipt; no campaign media is used.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import argparse
|
|
8
|
+
import hashlib
|
|
9
|
+
import json
|
|
10
|
+
from array import array
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from types import SimpleNamespace
|
|
13
|
+
|
|
14
|
+
from PIL import Image, ImageChops, ImageDraw
|
|
15
|
+
|
|
16
|
+
from framerudiments.projectm_backend import ProjectMBackend
|
|
17
|
+
from framerudiments.visualisers import ASSETS, ButterchurnBackend
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def digest(image):
|
|
21
|
+
return hashlib.sha256(image.tobytes()).hexdigest()
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def input_card(size):
|
|
25
|
+
image = Image.new("RGBA", size, "#10182f")
|
|
26
|
+
draw = ImageDraw.Draw(image)
|
|
27
|
+
draw.ellipse((8, 8, size[0] - 8, size[1] - 8), fill="#ff5b87")
|
|
28
|
+
draw.line((0, size[1] - 1, size[0] - 1, 0), fill="#4ee9ff", width=5)
|
|
29
|
+
return image
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def run_engine(name, factory, preset, output):
|
|
33
|
+
size = (96, 64)
|
|
34
|
+
pcm = array("f", [0.0] * 2048)
|
|
35
|
+
card = input_card(size)
|
|
36
|
+
transparent = Image.new("RGBA", size)
|
|
37
|
+
override = Image.new("RGBA", size, "#ff00ff")
|
|
38
|
+
|
|
39
|
+
def trial(writeback):
|
|
40
|
+
engine = factory(size, 60, preset, 0.72, 41, output)
|
|
41
|
+
try:
|
|
42
|
+
first = engine.process(card, SimpleNamespace(frame=0, output_seconds=0), pcm)
|
|
43
|
+
if writeback:
|
|
44
|
+
engine.set_feedback(override)
|
|
45
|
+
second = engine.process(
|
|
46
|
+
transparent, SimpleNamespace(frame=1, output_seconds=1 / 60), pcm
|
|
47
|
+
)
|
|
48
|
+
return first, second
|
|
49
|
+
finally:
|
|
50
|
+
engine.close()
|
|
51
|
+
|
|
52
|
+
first_control, control = trial(False)
|
|
53
|
+
first_treated, treated = trial(True)
|
|
54
|
+
try:
|
|
55
|
+
difference = ImageChops.difference(control.convert("RGB"), treated.convert("RGB"))
|
|
56
|
+
bbox = difference.getbbox()
|
|
57
|
+
if bbox is None:
|
|
58
|
+
raise AssertionError(f"{name} feedback writeback produced no next-frame pixel change")
|
|
59
|
+
control.save(output / f"{name}-control.png")
|
|
60
|
+
treated.save(output / f"{name}-feedback-hook.png")
|
|
61
|
+
difference.save(output / f"{name}-difference.png")
|
|
62
|
+
return {
|
|
63
|
+
"engine": name,
|
|
64
|
+
"frame0_hashes": [digest(first_control), digest(first_treated)],
|
|
65
|
+
"control_hash": digest(control),
|
|
66
|
+
"feedback_hook_hash": digest(treated),
|
|
67
|
+
"difference_bbox": list(bbox),
|
|
68
|
+
"difference_extrema": [list(value) for value in difference.getextrema()],
|
|
69
|
+
}
|
|
70
|
+
finally:
|
|
71
|
+
del first_control, first_treated, control, treated
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def write_receipt(path, receipt):
|
|
75
|
+
path.write_text(json.dumps(receipt, indent=2) + "\n", encoding="utf-8")
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def main():
|
|
79
|
+
parser = argparse.ArgumentParser()
|
|
80
|
+
parser.add_argument("--output", type=Path, required=True)
|
|
81
|
+
parser.add_argument(
|
|
82
|
+
"--engine",
|
|
83
|
+
choices=("all", "butterchurn", "projectm"),
|
|
84
|
+
default="all",
|
|
85
|
+
help="Run both engines or a single bounded recovery trial.",
|
|
86
|
+
)
|
|
87
|
+
args = parser.parse_args()
|
|
88
|
+
args.output.mkdir(parents=True, exist_ok=False)
|
|
89
|
+
receipt = {
|
|
90
|
+
"schema": "framerudiments.feedback-writeback-smoke.v1",
|
|
91
|
+
"synthetic": True,
|
|
92
|
+
"engines": [],
|
|
93
|
+
}
|
|
94
|
+
if args.engine in {"all", "butterchurn"}:
|
|
95
|
+
receipt["engines"].append(
|
|
96
|
+
run_engine(
|
|
97
|
+
"butterchurn",
|
|
98
|
+
ButterchurnBackend,
|
|
99
|
+
ASSETS / "feedback.json",
|
|
100
|
+
args.output,
|
|
101
|
+
)
|
|
102
|
+
)
|
|
103
|
+
write_receipt(args.output / "receipt.json", receipt)
|
|
104
|
+
if args.engine in {"all", "projectm"}:
|
|
105
|
+
receipt["engines"].append(
|
|
106
|
+
run_engine(
|
|
107
|
+
"projectm",
|
|
108
|
+
ProjectMBackend,
|
|
109
|
+
ASSETS / "feedback.milk",
|
|
110
|
+
args.output,
|
|
111
|
+
)
|
|
112
|
+
)
|
|
113
|
+
write_receipt(args.output / "receipt.json", receipt)
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
if __name__ == "__main__":
|
|
117
|
+
main()
|
|
@@ -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.8"
|
|
8
8
|
description = "Deterministic Shorts and lyric visualisers using DanceRudiments and StemLab evidence."
|
|
9
9
|
requires-python = ">=3.11"
|
|
10
10
|
readme = "README.md"
|
|
@@ -16,7 +16,7 @@ from .timeline import EventTrack
|
|
|
16
16
|
from .word_timing import WordTrack
|
|
17
17
|
from .rich_subtitles import RichTextAppearance
|
|
18
18
|
from .lyric_indicator import WordIndicator
|
|
19
|
-
from .visualisers import effect_specs
|
|
19
|
+
from .visualisers import effect_specs, VisualiserEffectHooks
|
|
20
20
|
from .scoped_effects import CompositingEffects
|
|
21
21
|
from .visualiser_routing import VisualiserLayerRouting
|
|
22
22
|
from .layer_masks import LayerMasks
|
|
@@ -378,6 +378,15 @@ class Compositor:
|
|
|
378
378
|
targets.add("word-caption")
|
|
379
379
|
if self.word_indicator:
|
|
380
380
|
targets.add("word-indicator")
|
|
381
|
+
matte_sources = {
|
|
382
|
+
spec["composite"]["matte"]["source"]
|
|
383
|
+
for spec in self.effect_specs
|
|
384
|
+
if spec.get("composite") and spec["composite"].get("matte")
|
|
385
|
+
}
|
|
386
|
+
unknown_mattes = matte_sources - targets
|
|
387
|
+
if unknown_mattes:
|
|
388
|
+
raise ValueError(f"Unknown visualiser matte sources: {sorted(unknown_mattes)}")
|
|
389
|
+
self.visualiser_hooks = VisualiserEffectHooks(project, self.effect_specs, self.context)
|
|
381
390
|
self.post_pipeline = CompositingEffects(
|
|
382
391
|
project, project.data.get("post_effects", {}), self.context, valid_elements=targets
|
|
383
392
|
)
|
|
@@ -386,7 +395,9 @@ class Compositor:
|
|
|
386
395
|
self.layer_masks = LayerMasks(project.data.get("layer_masks", {}), mask_order)
|
|
387
396
|
self.layer_animations = LayerAnimations(project.data.get("layer_animations", {}), mask_order)
|
|
388
397
|
self.layer_displacement = LayerDisplacement(project.data.get("layer_displacement", {}), mask_order)
|
|
389
|
-
self._isolate_stage_injections = bool(
|
|
398
|
+
self._isolate_stage_injections = bool(
|
|
399
|
+
project.data.get("visualiser_stage_injections") or matte_sources
|
|
400
|
+
)
|
|
390
401
|
self.visualiser_routing = None
|
|
391
402
|
if "visualiser_routing" in project.data:
|
|
392
403
|
if not self.effect_specs:
|
|
@@ -27,6 +27,7 @@ class ProjectMBackend:
|
|
|
27
27
|
self.glfw, self.gl = glfw, GL
|
|
28
28
|
self.window = self.handle = None
|
|
29
29
|
self.texture = self.fbo = self.output_texture = None
|
|
30
|
+
self.feedback_override = None
|
|
30
31
|
self.dll_dirs = []
|
|
31
32
|
self.registered = False
|
|
32
33
|
path = Path(os.environ.get("FRAMERUDIMENTS_PROJECTM_LIBRARY", ""))
|
|
@@ -225,7 +226,33 @@ class ProjectMBackend:
|
|
|
225
226
|
gl = self.gl
|
|
226
227
|
self.glfw.make_context_current(self.window)
|
|
227
228
|
self.lib.projectm_set_log_callback(self.log_callback, True, None)
|
|
229
|
+
if self.feedback_override is not None:
|
|
230
|
+
self._burn(self.feedback_override, 1.0)
|
|
231
|
+
self.feedback_override = None
|
|
228
232
|
frame = image.convert("RGBA")
|
|
233
|
+
self._burn(frame, self.mix)
|
|
234
|
+
self.lib.projectm_set_frame_time(self.handle, context.output_seconds)
|
|
235
|
+
audio = (C.c_float * len(pcm)).from_buffer_copy(pcm)
|
|
236
|
+
self.lib.projectm_pcm_add_float(self.handle, audio, len(pcm) // 2, 2)
|
|
237
|
+
self.lib.projectm_opengl_render_frame_fbo(self.handle, int(self.fbo))
|
|
238
|
+
self._check()
|
|
239
|
+
actual = self.lib.projectm_get_last_frame_time(self.handle)
|
|
240
|
+
if abs(actual - context.output_seconds) > 1e-9:
|
|
241
|
+
raise RuntimeError("projectM did not honour the offline frame clock")
|
|
242
|
+
if not capture:
|
|
243
|
+
return None
|
|
244
|
+
gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, self.fbo)
|
|
245
|
+
gl.glPixelStorei(gl.GL_PACK_ALIGNMENT, 1)
|
|
246
|
+
raw = gl.glReadPixels(0, 0, *self.size, gl.GL_RGBA, gl.GL_UNSIGNED_BYTE)
|
|
247
|
+
frame = Image.frombytes("RGBA", self.size, raw).transpose(Image.Transpose.FLIP_TOP_BOTTOM)
|
|
248
|
+
return frame.crop((self.crop_left, 0, self.crop_left + self.frame_size[0], self.frame_size[1]))
|
|
249
|
+
|
|
250
|
+
def _burn(self, image, opacity):
|
|
251
|
+
"""Burn an RGBA source into projectM's retained target through its public 4.2 API."""
|
|
252
|
+
gl = self.gl
|
|
253
|
+
frame = image.convert("RGBA")
|
|
254
|
+
if frame.size != self.frame_size:
|
|
255
|
+
raise ValueError("projectM feedback hook needs frame-sized RGBA")
|
|
229
256
|
if self.size != self.frame_size:
|
|
230
257
|
blank = (0, 0, 0, 0) if frame.getchannel("A").getextrema()[0] < 255 else "black"
|
|
231
258
|
canvas = Image.new("RGBA", self.size, blank)
|
|
@@ -233,7 +260,7 @@ class ProjectMBackend:
|
|
|
233
260
|
frame = canvas
|
|
234
261
|
# Preserve source alpha so an unfed layer cannot paint black into feedback.
|
|
235
262
|
# Fully opaque legacy inputs remain numerically unchanged.
|
|
236
|
-
frame.putalpha(frame.getchannel("A").point(lambda a: round(a *
|
|
263
|
+
frame.putalpha(frame.getchannel("A").point(lambda a: round(a * opacity)))
|
|
237
264
|
pixels = frame.transpose(Image.Transpose.FLIP_TOP_BOTTOM).tobytes()
|
|
238
265
|
gl.glActiveTexture(gl.GL_TEXTURE0)
|
|
239
266
|
gl.glBindTexture(gl.GL_TEXTURE_2D, self.texture)
|
|
@@ -243,22 +270,12 @@ class ProjectMBackend:
|
|
|
243
270
|
gl.glEnable(gl.GL_BLEND)
|
|
244
271
|
gl.glBlendEquation(gl.GL_FUNC_ADD)
|
|
245
272
|
gl.glBlendFunc(gl.GL_SRC_ALPHA, gl.GL_ONE_MINUS_SRC_ALPHA)
|
|
246
|
-
self.lib.projectm_set_frame_time(self.handle, context.output_seconds)
|
|
247
273
|
self.lib.projectm_opengl_burn_texture(self.handle, int(self.texture), 0, 0, *self.size)
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
if abs(actual - context.output_seconds) > 1e-9:
|
|
254
|
-
raise RuntimeError("projectM did not honour the offline frame clock")
|
|
255
|
-
if not capture:
|
|
256
|
-
return None
|
|
257
|
-
gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, self.fbo)
|
|
258
|
-
gl.glPixelStorei(gl.GL_PACK_ALIGNMENT, 1)
|
|
259
|
-
raw = gl.glReadPixels(0, 0, *self.size, gl.GL_RGBA, gl.GL_UNSIGNED_BYTE)
|
|
260
|
-
frame = Image.frombytes("RGBA", self.size, raw).transpose(Image.Transpose.FLIP_TOP_BOTTOM)
|
|
261
|
-
return frame.crop((self.crop_left, 0, self.crop_left + self.frame_size[0], self.frame_size[1]))
|
|
274
|
+
|
|
275
|
+
def set_feedback(self, image):
|
|
276
|
+
if image.mode != "RGBA" or image.size != self.frame_size:
|
|
277
|
+
raise ValueError("projectM feedback hook needs frame-sized RGBA")
|
|
278
|
+
self.feedback_override = image.copy()
|
|
262
279
|
|
|
263
280
|
def close(self):
|
|
264
281
|
global _users
|
|
@@ -142,7 +142,14 @@ def preflight(
|
|
|
142
142
|
effects = None
|
|
143
143
|
try:
|
|
144
144
|
effects = (
|
|
145
|
-
EffectChain(
|
|
145
|
+
EffectChain(
|
|
146
|
+
p,
|
|
147
|
+
compositor.effect_specs,
|
|
148
|
+
ffmpeg,
|
|
149
|
+
out,
|
|
150
|
+
routing=compositor.visualiser_routing,
|
|
151
|
+
hooks=compositor.visualiser_hooks,
|
|
152
|
+
)
|
|
146
153
|
if compositor.effect_specs
|
|
147
154
|
else None
|
|
148
155
|
)
|
|
@@ -201,7 +208,14 @@ def render(project, output, *, ffmpeg=None, ffprobe=None, scene=None, renderers=
|
|
|
201
208
|
effects = None
|
|
202
209
|
try:
|
|
203
210
|
effects = (
|
|
204
|
-
EffectChain(
|
|
211
|
+
EffectChain(
|
|
212
|
+
p,
|
|
213
|
+
compositor.effect_specs,
|
|
214
|
+
ffmpeg,
|
|
215
|
+
out,
|
|
216
|
+
routing=compositor.visualiser_routing,
|
|
217
|
+
hooks=compositor.visualiser_hooks,
|
|
218
|
+
)
|
|
205
219
|
if compositor.effect_specs
|
|
206
220
|
else None
|
|
207
221
|
)
|
|
@@ -84,13 +84,19 @@ async function initialise(config) {
|
|
|
84
84
|
gl.activeTexture(gl.TEXTURE0);
|
|
85
85
|
const oldTexture = gl.getParameter(gl.TEXTURE_BINDING_2D), oldSampler = gl.getParameter(gl.SAMPLER_BINDING);
|
|
86
86
|
gl.bindSampler(0, null); gl.bindTexture(gl.TEXTURE_2D, texture);
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
87
|
+
const inject = (rgba, opacity) => {
|
|
88
|
+
if (rgba) gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, config.width, config.height, 0,
|
|
89
|
+
gl.RGBA, gl.UNSIGNED_BYTE, decode(rgba));
|
|
90
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, r.targetFrameBuffer);
|
|
91
|
+
gl.viewport(0, 0, r.texsizeX, r.texsizeY);
|
|
92
|
+
gl.useProgram(program); gl.bindVertexArray(vao); gl.uniform1i(source, 0); gl.uniform1f(amount, opacity);
|
|
93
|
+
gl.enable(gl.BLEND); gl.blendEquation(gl.FUNC_ADD); gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
|
|
94
|
+
gl.drawArrays(gl.TRIANGLES, 0, 3);
|
|
95
|
+
};
|
|
96
|
+
// A feedback hook replaces the retained target before this frame consumes
|
|
97
|
+
// it. The ordinary source is then injected with its independent input mix.
|
|
98
|
+
if (message.feedback_rgba) inject(message.feedback_rgba, 1);
|
|
99
|
+
inject(message.rgba, config.mix);
|
|
94
100
|
gl.bindVertexArray(state.vao); gl.useProgram(state.program);
|
|
95
101
|
gl.bindTexture(gl.TEXTURE_2D, oldTexture); gl.bindSampler(0, oldSampler); gl.activeTexture(state.active);
|
|
96
102
|
gl.bindFramebuffer(gl.FRAMEBUFFER, state.fbo); gl.viewport(...state.viewport);
|