vizcore 0.1.0 → 1.1.0
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.
- checksums.yaml +4 -4
- data/README.md +70 -117
- data/docs/.nojekyll +0 -0
- data/docs/assets/playground-worker.js +373 -0
- data/docs/assets/playground.css +440 -0
- data/docs/assets/playground.js +652 -0
- data/docs/assets/site.css +744 -0
- data/docs/assets/vizcore-demo.gif +0 -0
- data/docs/assets/vizcore-poster.png +0 -0
- data/docs/assets/vj-tunnel.js +159 -0
- data/docs/index.html +225 -0
- data/docs/playground.html +81 -0
- data/docs/shape_dsl.md +269 -0
- data/examples/README.md +59 -0
- data/examples/assets/README.md +19 -0
- data/examples/audio_inspector.rb +34 -0
- data/examples/club_intro_drop.rb +78 -0
- data/examples/kansai_rubykaigi_visual.rb +70 -0
- data/examples/live_coding_minimal.rb +22 -0
- data/examples/midi_controller_show.rb +78 -0
- data/examples/midi_scene_switch.rb +3 -1
- data/examples/parser_visualizer.rb +48 -0
- data/examples/readme_demo.rb +17 -0
- data/examples/rhythm_geometry.rb +34 -0
- data/examples/ruby_crystal_show.rb +35 -0
- data/examples/shader_playground.rb +18 -0
- data/examples/unyo_liquid.rb +59 -0
- data/examples/vj_ambient_chill_room.rb +124 -0
- data/examples/vj_dnb_jungle.rb +170 -0
- data/examples/vj_festival_mainstage.rb +245 -0
- data/examples/vj_festival_mainstage.yml +17 -0
- data/examples/vj_glitch_industrial.rb +164 -0
- data/examples/vj_hiphop_cipher.rb +167 -0
- data/examples/vj_jpop_idol_live.rb +210 -0
- data/examples/vj_synthwave_retro.rb +173 -0
- data/examples/vj_techno_warehouse.rb +195 -0
- data/frontend/index.html +494 -2
- data/frontend/src/audio-inspector.js +40 -0
- data/frontend/src/custom-shape-param-controls.js +106 -0
- data/frontend/src/live-controls.js +131 -0
- data/frontend/src/main.js +1060 -16
- data/frontend/src/mapping-target-selector.js +109 -0
- data/frontend/src/midi-learn.js +194 -0
- data/frontend/src/performance-monitor.js +183 -0
- data/frontend/src/plugin-runtime.js +130 -0
- data/frontend/src/projector-mode.js +56 -0
- data/frontend/src/renderer/engine.js +157 -3
- data/frontend/src/renderer/layer-manager.js +442 -30
- data/frontend/src/renderer/shader-manager.js +26 -0
- data/frontend/src/runtime-control-preset.js +11 -0
- data/frontend/src/shader-error-overlay.js +29 -0
- data/frontend/src/shader-param-controls.js +93 -0
- data/frontend/src/shaders/builtins.js +380 -2
- data/frontend/src/shaders/post-effects.js +52 -0
- data/frontend/src/shape-editor-controls.js +157 -0
- data/frontend/src/visual-regression.js +67 -0
- data/frontend/src/visual-settings-preset.js +103 -0
- data/frontend/src/visuals/geometry.js +666 -0
- data/frontend/src/visuals/image-renderer.js +291 -0
- data/frontend/src/visuals/particle-system.js +56 -10
- data/frontend/src/visuals/shape-renderer.js +475 -0
- data/frontend/src/visuals/spectrogram-renderer.js +226 -0
- data/frontend/src/visuals/svg-arc.js +104 -0
- data/frontend/src/visuals/text-renderer.js +112 -11
- data/frontend/src/websocket-client.js +12 -1
- data/lib/vizcore/analysis/adaptive_normalizer.rb +70 -0
- data/lib/vizcore/analysis/beat_detector.rb +4 -2
- data/lib/vizcore/analysis/bpm_estimator.rb +8 -0
- data/lib/vizcore/analysis/feature_recorder.rb +159 -0
- data/lib/vizcore/analysis/feature_replay.rb +84 -0
- data/lib/vizcore/analysis/pipeline.rb +235 -11
- data/lib/vizcore/analysis/tap_tempo.rb +74 -0
- data/lib/vizcore/analysis.rb +4 -0
- data/lib/vizcore/audio/dummy_sine_input.rb +1 -1
- data/lib/vizcore/audio/fixture_input.rb +65 -0
- data/lib/vizcore/audio/input_manager.rb +4 -2
- data/lib/vizcore/audio/mic_input.rb +24 -8
- data/lib/vizcore/audio/portaudio_ffi.rb +106 -1
- data/lib/vizcore/audio.rb +1 -0
- data/lib/vizcore/cli/doctor.rb +159 -0
- data/lib/vizcore/cli/dsl_reference.rb +99 -0
- data/lib/vizcore/cli/layer_docs.rb +46 -0
- data/lib/vizcore/cli/scene_diagnostics.rb +23 -0
- data/lib/vizcore/cli/scene_inspector.rb +136 -0
- data/lib/vizcore/cli/scene_validator.rb +337 -0
- data/lib/vizcore/cli/shader_template.rb +68 -0
- data/lib/vizcore/cli/shader_uniform_docs.rb +54 -0
- data/lib/vizcore/cli.rb +689 -18
- data/lib/vizcore/config.rb +103 -2
- data/lib/vizcore/control_preset.rb +68 -0
- data/lib/vizcore/dsl/engine.rb +277 -5
- data/lib/vizcore/dsl/layer_builder.rb +1280 -23
- data/lib/vizcore/dsl/layer_group_builder.rb +112 -0
- data/lib/vizcore/dsl/mapping_resolver.rb +290 -7
- data/lib/vizcore/dsl/mapping_transform_builder.rb +71 -0
- data/lib/vizcore/dsl/reaction_builder.rb +44 -0
- data/lib/vizcore/dsl/scene_builder.rb +61 -5
- data/lib/vizcore/dsl/shader_source_resolver.rb +67 -6
- data/lib/vizcore/dsl/style_builder.rb +68 -0
- data/lib/vizcore/dsl/timeline_builder.rb +138 -0
- data/lib/vizcore/dsl/transition_controller.rb +77 -0
- data/lib/vizcore/dsl.rb +5 -1
- data/lib/vizcore/layer_catalog.rb +275 -0
- data/lib/vizcore/project_manifest.rb +152 -0
- data/lib/vizcore/renderer/png_writer.rb +57 -0
- data/lib/vizcore/renderer/render_sequence.rb +153 -0
- data/lib/vizcore/renderer/scene_frame_source.rb +132 -0
- data/lib/vizcore/renderer/scene_serializer.rb +36 -3
- data/lib/vizcore/renderer/snapshot.rb +38 -0
- data/lib/vizcore/renderer/snapshot_renderer.rb +938 -0
- data/lib/vizcore/renderer.rb +5 -0
- data/lib/vizcore/server/frame_broadcaster.rb +143 -8
- data/lib/vizcore/server/gallery_app.rb +155 -0
- data/lib/vizcore/server/gallery_page.rb +100 -0
- data/lib/vizcore/server/gallery_runner.rb +48 -0
- data/lib/vizcore/server/rack_app.rb +203 -4
- data/lib/vizcore/server/runner.rb +391 -22
- data/lib/vizcore/server/scene_dependency_watcher.rb +79 -0
- data/lib/vizcore/server/websocket_handler.rb +60 -10
- data/lib/vizcore/server.rb +4 -0
- data/lib/vizcore/shape.rb +719 -0
- data/lib/vizcore/sync/osc_message.rb +103 -0
- data/lib/vizcore/sync/osc_receiver.rb +68 -0
- data/lib/vizcore/sync.rb +4 -0
- data/lib/vizcore/templates/midi_control_scene.rb +3 -1
- data/lib/vizcore/templates/plugin_layer.rb +20 -0
- data/lib/vizcore/templates/plugin_readme.md +23 -0
- data/lib/vizcore/templates/plugin_renderer.js +43 -0
- data/lib/vizcore/templates/plugin_scene.rb +14 -0
- data/lib/vizcore/templates/project_readme.md +7 -23
- data/lib/vizcore/templates/rubykaigi_scene.rb +30 -0
- data/lib/vizcore/version.rb +1 -1
- data/lib/vizcore.rb +28 -0
- data/scripts/browser_capture.mjs +75 -0
- data/sig/vizcore.rbs +461 -0
- metadata +94 -3
- data/docs/GETTING_STARTED.md +0 -105
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../analysis"
|
|
4
|
+
require_relative "../audio"
|
|
5
|
+
require_relative "../dsl"
|
|
6
|
+
|
|
7
|
+
module Vizcore
|
|
8
|
+
module Renderer
|
|
9
|
+
# Produces analyzed scene frames for offline renderers.
|
|
10
|
+
class SceneFrameSource
|
|
11
|
+
def initialize(config:, frame_rate: nil)
|
|
12
|
+
@config = config
|
|
13
|
+
@frame_rate = frame_rate
|
|
14
|
+
@shader_source_resolver = Vizcore::DSL::ShaderSourceResolver.new
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# @return [Vizcore::Renderer::SceneFrameSource]
|
|
18
|
+
def start
|
|
19
|
+
@definition = resolve_shader_sources(Vizcore::DSL::Engine.load_file(@config.scene_file.to_s))
|
|
20
|
+
@scene = first_scene(@definition)
|
|
21
|
+
@input_manager = build_input_manager
|
|
22
|
+
@input_manager.start
|
|
23
|
+
@capture_size = capture_size
|
|
24
|
+
@pipeline = build_pipeline
|
|
25
|
+
@frame_count = 0
|
|
26
|
+
self
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# @return [Hash] frame data consumed by offline renderers
|
|
30
|
+
def capture
|
|
31
|
+
ensure_started!
|
|
32
|
+
|
|
33
|
+
audio = @pipeline.call(@input_manager.capture_frame(@capture_size))
|
|
34
|
+
@frame_count += 1
|
|
35
|
+
layers = Vizcore::DSL::MappingResolver.new.resolve_layers(
|
|
36
|
+
scene_layers: @scene[:layers],
|
|
37
|
+
audio: audio,
|
|
38
|
+
time: frame_time,
|
|
39
|
+
frame: @frame_count
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
{
|
|
43
|
+
scene: { name: @scene[:name], layers: layers },
|
|
44
|
+
audio: audio,
|
|
45
|
+
scene_name: @scene[:name].to_s
|
|
46
|
+
}
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# @return [void]
|
|
50
|
+
def stop
|
|
51
|
+
@input_manager&.stop
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
private
|
|
55
|
+
|
|
56
|
+
def resolve_shader_sources(definition)
|
|
57
|
+
@shader_source_resolver.resolve(definition: definition, scene_file: @config.scene_file.to_s)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def first_scene(definition)
|
|
61
|
+
scene = Array(definition[:scenes]).first
|
|
62
|
+
return scene if scene
|
|
63
|
+
|
|
64
|
+
{ name: @config.scene_file.basename(".rb").to_sym, layers: [] }
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def build_input_manager
|
|
68
|
+
Vizcore::Audio::InputManager.new(
|
|
69
|
+
source: @config.audio_source,
|
|
70
|
+
file_path: @config.audio_file&.to_s,
|
|
71
|
+
audio_device: @config.audio_device
|
|
72
|
+
)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def capture_size
|
|
76
|
+
return @input_manager.frame_size unless @frame_rate
|
|
77
|
+
|
|
78
|
+
@input_manager.realtime_capture_size(@frame_rate)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def build_pipeline
|
|
82
|
+
Vizcore::Analysis::Pipeline.new(
|
|
83
|
+
sample_rate: @input_manager.sample_rate,
|
|
84
|
+
fft_size: supported_fft_size(@input_manager.frame_size),
|
|
85
|
+
noise_gate: @config.noise_gate,
|
|
86
|
+
audio_normalize: audio_normalize_settings,
|
|
87
|
+
bpm: bpm_setting,
|
|
88
|
+
bpm_lock: bpm_lock_setting
|
|
89
|
+
)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def frame_time
|
|
93
|
+
return 0.0 unless @frame_rate
|
|
94
|
+
|
|
95
|
+
(@frame_count - 1).fdiv(@frame_rate)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def audio_normalize_settings
|
|
99
|
+
Hash(@definition[:analysis] || {})[:audio_normalize]
|
|
100
|
+
rescue StandardError
|
|
101
|
+
nil
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def bpm_setting
|
|
105
|
+
@config.bpm || Hash(@definition[:analysis] || {})[:bpm]
|
|
106
|
+
rescue StandardError
|
|
107
|
+
@config.bpm
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def bpm_lock_setting
|
|
111
|
+
@config.bpm_lock? || !!Hash(@definition[:analysis] || {})[:bpm_lock]
|
|
112
|
+
rescue StandardError
|
|
113
|
+
@config.bpm_lock?
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def supported_fft_size(size)
|
|
117
|
+
value = Integer(size)
|
|
118
|
+
return value if value.positive? && (value & (value - 1)).zero?
|
|
119
|
+
|
|
120
|
+
1024
|
|
121
|
+
rescue StandardError
|
|
122
|
+
1024
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def ensure_started!
|
|
126
|
+
return if @input_manager && @pipeline && @scene
|
|
127
|
+
|
|
128
|
+
raise RuntimeError, "scene frame source has not been started"
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
end
|
|
@@ -9,28 +9,39 @@ module Vizcore
|
|
|
9
9
|
# @param scene_name [String, Symbol]
|
|
10
10
|
# @param scene_layers [Array<Hash>]
|
|
11
11
|
# @param transition [Hash, nil]
|
|
12
|
+
# @param metrics [Hash, nil]
|
|
12
13
|
# @return [Hash]
|
|
13
|
-
def audio_frame(timestamp:, audio:, scene_name:, scene_layers:, transition: nil)
|
|
14
|
-
{
|
|
14
|
+
def audio_frame(timestamp:, audio:, scene_name:, scene_layers:, transition: nil, metrics: nil)
|
|
15
|
+
frame = {
|
|
15
16
|
timestamp: Float(timestamp),
|
|
16
17
|
audio: serialize_audio(audio),
|
|
17
18
|
scene: serialize_scene(scene_name, scene_layers),
|
|
18
19
|
transition: transition
|
|
19
20
|
}
|
|
21
|
+
frame[:metrics] = serialize_metrics(metrics) if metrics
|
|
22
|
+
frame
|
|
20
23
|
end
|
|
21
24
|
|
|
22
25
|
private
|
|
23
26
|
|
|
24
27
|
def serialize_audio(audio)
|
|
25
28
|
bands = symbolize_hash(audio[:bands])
|
|
29
|
+
onsets = { sub: 0.0, low: 0.0, mid: 0.0, high: 0.0 }.merge(symbolize_hash(audio[:onsets]))
|
|
30
|
+
drums = { kick: 0.0, snare: 0.0, hihat: 0.0 }.merge(symbolize_hash(audio[:drums]))
|
|
26
31
|
|
|
27
32
|
{
|
|
28
33
|
amplitude: round_float(audio[:amplitude]),
|
|
29
34
|
bands: bands.transform_values { |value| round_float(value) },
|
|
30
35
|
fft: Array(audio[:fft]).map { |value| round_float(value) },
|
|
36
|
+
onset: round_float(audio[:onset]),
|
|
37
|
+
onsets: onsets.transform_values { |value| round_float(value) },
|
|
38
|
+
drums: drums.transform_values { |value| round_float(value) },
|
|
31
39
|
beat: !!audio[:beat],
|
|
40
|
+
beat_confidence: round_float(audio[:beat_confidence]),
|
|
41
|
+
beat_pulse: round_float(audio[:beat_pulse]),
|
|
32
42
|
beat_count: Integer(audio[:beat_count] || 0),
|
|
33
|
-
bpm: audio[:bpm]
|
|
43
|
+
bpm: audio[:bpm],
|
|
44
|
+
peak_frequency: round_float(audio[:peak_frequency])
|
|
34
45
|
}
|
|
35
46
|
end
|
|
36
47
|
|
|
@@ -52,9 +63,31 @@ module Vizcore
|
|
|
52
63
|
output[:shader] = values[:shader].to_s if values[:shader]
|
|
53
64
|
output[:glsl] = values[:glsl].to_s if values[:glsl]
|
|
54
65
|
output[:glsl_source] = values[:glsl_source].to_s if values[:glsl_source]
|
|
66
|
+
output[:param_schema] = serialize_param_schema(values[:param_schema]) if values[:param_schema]
|
|
55
67
|
output
|
|
56
68
|
end
|
|
57
69
|
|
|
70
|
+
def serialize_param_schema(schema)
|
|
71
|
+
Array(schema).map do |entry|
|
|
72
|
+
values = symbolize_hash(entry)
|
|
73
|
+
{
|
|
74
|
+
name: values.fetch(:name).to_s,
|
|
75
|
+
default: round_float(values[:default]),
|
|
76
|
+
min: round_float(values[:min]),
|
|
77
|
+
max: round_float(values[:max]),
|
|
78
|
+
step: round_float(values[:step])
|
|
79
|
+
}.compact
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def serialize_metrics(metrics)
|
|
84
|
+
symbolize_hash(metrics).each_with_object({}) do |(key, value), output|
|
|
85
|
+
output[key] = key == :frame_id ? Integer(value) : round_float(value)
|
|
86
|
+
rescue StandardError
|
|
87
|
+
output[key] = 0
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
58
91
|
def symbolize_hash(value)
|
|
59
92
|
Hash(value).each_with_object({}) do |(key, entry), output|
|
|
60
93
|
output[key.to_sym] = entry
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "fileutils"
|
|
4
|
+
require "pathname"
|
|
5
|
+
require_relative "scene_frame_source"
|
|
6
|
+
require_relative "snapshot_renderer"
|
|
7
|
+
|
|
8
|
+
module Vizcore
|
|
9
|
+
module Renderer
|
|
10
|
+
# Builds one analyzed scene frame and writes a PNG preview.
|
|
11
|
+
class Snapshot
|
|
12
|
+
def initialize(config:, width: SnapshotRenderer::DEFAULT_WIDTH, height: SnapshotRenderer::DEFAULT_HEIGHT)
|
|
13
|
+
@config = config
|
|
14
|
+
@width = width
|
|
15
|
+
@height = height
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# @param out [String, Pathname]
|
|
19
|
+
# @return [Hash] snapshot metadata
|
|
20
|
+
def write(out:)
|
|
21
|
+
output_path = Pathname.new(out.to_s).expand_path
|
|
22
|
+
frame_source = SceneFrameSource.new(config: @config)
|
|
23
|
+
frame_source.start
|
|
24
|
+
frame = frame_source.capture
|
|
25
|
+
png = SnapshotRenderer.new(width: @width, height: @height).render(
|
|
26
|
+
scene: frame.fetch(:scene),
|
|
27
|
+
audio: frame.fetch(:audio)
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
FileUtils.mkdir_p(output_path.dirname)
|
|
31
|
+
File.binwrite(output_path, png)
|
|
32
|
+
{ path: output_path, scene: frame.fetch(:scene_name), width: @width, height: @height }
|
|
33
|
+
ensure
|
|
34
|
+
frame_source&.stop
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|