vizcore 1.1.0 → 1.2.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/frontend/index.html +24 -2
- data/frontend/src/audio-inspector.js +9 -0
- data/frontend/src/live-controls.js +219 -7
- data/frontend/src/main.js +447 -57
- data/frontend/src/midi-learn.js +22 -2
- data/frontend/src/performance-monitor.js +137 -1
- data/frontend/src/renderer/engine.js +391 -10
- data/frontend/src/renderer/layer-manager.js +472 -71
- data/frontend/src/runtime-control-preset.js +44 -0
- data/frontend/src/scene-patches.js +159 -0
- data/frontend/src/shader-error-overlay.js +1 -0
- data/frontend/src/visuals/image-renderer.js +19 -0
- data/frontend/src/visuals/particle-system.js +10 -0
- data/frontend/src/visuals/shape-renderer.js +13 -0
- data/frontend/src/visuals/spectrogram-renderer.js +14 -0
- data/frontend/src/visuals/text-renderer.js +13 -0
- data/frontend/src/websocket-client.js +6 -0
- data/lib/vizcore/analysis/adaptive_normalizer.rb +20 -2
- data/lib/vizcore/analysis/bpm_estimator.rb +18 -8
- data/lib/vizcore/analysis/feature_recorder.rb +117 -7
- data/lib/vizcore/analysis/feature_replay.rb +48 -9
- data/lib/vizcore/analysis/pipeline.rb +258 -9
- data/lib/vizcore/analysis/tap_tempo.rb +17 -2
- data/lib/vizcore/audio/calibration.rb +156 -0
- data/lib/vizcore/audio/file_input.rb +28 -0
- data/lib/vizcore/audio/input_manager.rb +36 -1
- data/lib/vizcore/audio/midi_input.rb +5 -0
- data/lib/vizcore/audio/ring_buffer.rb +22 -0
- data/lib/vizcore/audio.rb +1 -0
- data/lib/vizcore/cli/dsl_reference.rb +64 -8
- data/lib/vizcore/cli/plugin_checker.rb +93 -0
- data/lib/vizcore/cli/scene_diagnostics.rb +2 -2
- data/lib/vizcore/cli/scene_inspector.rb +35 -1
- data/lib/vizcore/cli/scene_validator.rb +487 -39
- data/lib/vizcore/cli/shader_template.rb +7 -2
- data/lib/vizcore/cli/shader_uniform_docs.rb +11 -0
- data/lib/vizcore/cli.rb +268 -15
- data/lib/vizcore/config.rb +40 -3
- data/lib/vizcore/control_preset.rb +29 -0
- data/lib/vizcore/deep_copy.rb +21 -0
- data/lib/vizcore/dsl/color_helpers.rb +155 -0
- data/lib/vizcore/dsl/engine.rb +219 -23
- data/lib/vizcore/dsl/layer_builder.rb +278 -15
- data/lib/vizcore/dsl/layer_group_builder.rb +10 -12
- data/lib/vizcore/dsl/layout_helpers.rb +290 -0
- data/lib/vizcore/dsl/mapping_preset_builder.rb +41 -0
- data/lib/vizcore/dsl/mapping_resolver.rb +404 -22
- data/lib/vizcore/dsl/mapping_transform_builder.rb +50 -0
- data/lib/vizcore/dsl/midi_map_executor.rb +219 -23
- data/lib/vizcore/dsl/reaction_builder.rb +1 -0
- data/lib/vizcore/dsl/scene_builder.rb +83 -13
- data/lib/vizcore/dsl/shader_source_resolver.rb +1 -10
- data/lib/vizcore/dsl/style_builder.rb +3 -0
- data/lib/vizcore/dsl/timeline_builder.rb +91 -8
- data/lib/vizcore/dsl/transition_controller.rb +157 -18
- data/lib/vizcore/dsl.rb +2 -0
- data/lib/vizcore/layer_catalog.rb +1 -0
- data/lib/vizcore/plugin_asset_policy.rb +55 -0
- data/lib/vizcore/project_manifest.rb +12 -2
- data/lib/vizcore/renderer/render_sequence.rb +104 -13
- data/lib/vizcore/renderer/scene_frame_source.rb +179 -14
- data/lib/vizcore/renderer/scene_serializer.rb +38 -0
- data/lib/vizcore/renderer/snapshot.rb +4 -3
- data/lib/vizcore/renderer/snapshot_renderer.rb +134 -8
- data/lib/vizcore/scene_trust.rb +31 -0
- data/lib/vizcore/server/frame_broadcaster.rb +469 -23
- data/lib/vizcore/server/rack_app.rb +151 -4
- data/lib/vizcore/server/runner.rb +676 -82
- data/lib/vizcore/server/websocket_handler.rb +236 -14
- data/lib/vizcore/server.rb +21 -0
- data/lib/vizcore/shape.rb +39 -16
- data/lib/vizcore/sync/osc_message.rb +66 -9
- data/lib/vizcore/version.rb +1 -1
- data/lib/vizcore.rb +33 -0
- data/scripts/browser_capture.mjs +31 -2
- data/sig/vizcore.rbs +55 -4
- metadata +18 -3
|
@@ -2,7 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
require_relative "mapping_transform_builder"
|
|
4
4
|
require_relative "reaction_builder"
|
|
5
|
+
require_relative "../deep_copy"
|
|
6
|
+
require_relative "../layer_catalog"
|
|
5
7
|
require_relative "../shape"
|
|
8
|
+
require_relative "color_helpers"
|
|
9
|
+
require_relative "layout_helpers"
|
|
6
10
|
|
|
7
11
|
module Vizcore
|
|
8
12
|
module DSL
|
|
@@ -11,7 +15,9 @@ module Vizcore
|
|
|
11
15
|
NO_ARGUMENT = Object.new.freeze
|
|
12
16
|
SHAPE_SCHEMA_VERSION = 2
|
|
13
17
|
MAPPING_SOURCE_KINDS = %i[
|
|
14
|
-
amplitude frequency_band fft_spectrum onset kick snare hihat beat beat_confidence beat_pulse beat_count bpm
|
|
18
|
+
amplitude peak frequency_band frequency_band_peak fft_spectrum onset kick snare hihat beat beat_confidence beat_pulse beat_count bpm
|
|
19
|
+
beat_phase beat_2 beat_4 beat_8 beat_triplet triplet bar_phase bar_count phrase_count bpm_confidence
|
|
20
|
+
spectral_centroid spectral_rolloff spectral_flatness spectral_flux zero_crossing_rate global lfo adsr envelope
|
|
15
21
|
].freeze
|
|
16
22
|
PATH_DEFAULT_DETAIL = 32
|
|
17
23
|
PATH_MIN_DETAIL = 4
|
|
@@ -30,6 +36,9 @@ module Vizcore
|
|
|
30
36
|
}.freeze
|
|
31
37
|
SHAPE_STYLE_KEYS = Vizcore::Shape::STYLE_KEYS
|
|
32
38
|
SHAPE_TRANSFORM_KEYS = %i[translate rotate rotation scale origin].freeze
|
|
39
|
+
STRICT_PARAM_ALLOWLIST = %i[
|
|
40
|
+
custom_shape_controls custom_shapes glsl_source group origin rotate scale translate
|
|
41
|
+
].freeze
|
|
33
42
|
|
|
34
43
|
# Reference to an already declared shape, used by `map ... to: shape(:id).radius`.
|
|
35
44
|
class ShapeReference
|
|
@@ -52,9 +61,13 @@ module Vizcore
|
|
|
52
61
|
# @param name [Symbol, String] layer identifier
|
|
53
62
|
# @param styles [Hash] reusable layer parameter styles
|
|
54
63
|
# @param defaults [Hash] default params applied before layer-specific values
|
|
55
|
-
|
|
64
|
+
# @param strict [Boolean] true when unknown layer params should fail
|
|
65
|
+
# @param mapping_presets [Hash] reusable mapping presets
|
|
66
|
+
def initialize(name:, styles: {}, defaults: {}, mapping_presets: {}, strict: false)
|
|
56
67
|
@name = name.to_sym
|
|
57
68
|
@styles = styles
|
|
69
|
+
@mapping_presets = mapping_presets
|
|
70
|
+
@strict = !!strict
|
|
58
71
|
@type = nil
|
|
59
72
|
@shader = nil
|
|
60
73
|
@glsl = nil
|
|
@@ -65,6 +78,9 @@ module Vizcore
|
|
|
65
78
|
@shape_group_stack = [{}]
|
|
66
79
|
end
|
|
67
80
|
|
|
81
|
+
include ColorHelpers
|
|
82
|
+
include LayoutHelpers
|
|
83
|
+
|
|
68
84
|
# Evaluate a layer block.
|
|
69
85
|
#
|
|
70
86
|
# @yield Layer DSL methods
|
|
@@ -522,7 +538,12 @@ module Vizcore
|
|
|
522
538
|
# @return [ShapeReference]
|
|
523
539
|
def shape(id)
|
|
524
540
|
key = id.to_sym
|
|
525
|
-
index = @shape_index_by_id.fetch(key)
|
|
541
|
+
index = @shape_index_by_id.fetch(key) do
|
|
542
|
+
suggestion_message = shape_id_suggestions(key)
|
|
543
|
+
message = "unknown shape id: #{key.inspect}"
|
|
544
|
+
message = "#{message}. Did you mean: #{suggestion_message}" unless suggestion_message.empty?
|
|
545
|
+
raise ArgumentError, message
|
|
546
|
+
end
|
|
526
547
|
ShapeReference.new("shapes.#{index}")
|
|
527
548
|
end
|
|
528
549
|
|
|
@@ -567,6 +588,17 @@ module Vizcore
|
|
|
567
588
|
@params[:palette] = normalize_palette(colors)
|
|
568
589
|
end
|
|
569
590
|
|
|
591
|
+
# Append one post effect to this layer's post effect chain.
|
|
592
|
+
#
|
|
593
|
+
# @param name [Symbol, String] effect key
|
|
594
|
+
# @return [Symbol]
|
|
595
|
+
def post(name)
|
|
596
|
+
raise ArgumentError, "post expects a symbol or string" unless name
|
|
597
|
+
|
|
598
|
+
@params[:post_effects] ||= []
|
|
599
|
+
@params[:post_effects] << name.to_sym
|
|
600
|
+
end
|
|
601
|
+
|
|
570
602
|
# Apply a named style by merging its params into this layer.
|
|
571
603
|
#
|
|
572
604
|
# @param name [Symbol, String] style identifier
|
|
@@ -636,6 +668,17 @@ module Vizcore
|
|
|
636
668
|
end
|
|
637
669
|
end
|
|
638
670
|
|
|
671
|
+
# Apply a named mapping preset to this layer.
|
|
672
|
+
#
|
|
673
|
+
# @param name [Symbol, String] mapping preset identifier
|
|
674
|
+
# @raise [ArgumentError] when the preset is unknown
|
|
675
|
+
# @return [void]
|
|
676
|
+
def use_mapping(name)
|
|
677
|
+
preset_name = name.to_sym
|
|
678
|
+
preset = @mapping_presets.fetch(preset_name) { raise ArgumentError, "unknown mapping preset: #{preset_name}" }
|
|
679
|
+
preset.each { |mapping| @mappings << deep_dup(mapping) }
|
|
680
|
+
end
|
|
681
|
+
|
|
639
682
|
# High-level mapping DSL for describing audio reactions inside a layer.
|
|
640
683
|
#
|
|
641
684
|
# @param source_value [Hash, Symbol, String] analysis source descriptor
|
|
@@ -663,42 +706,82 @@ module Vizcore
|
|
|
663
706
|
mapping_source(:amplitude)
|
|
664
707
|
end
|
|
665
708
|
|
|
709
|
+
# @return [Hash] source descriptor for absolute sample peak level
|
|
710
|
+
def peak
|
|
711
|
+
mapping_source(:peak)
|
|
712
|
+
end
|
|
713
|
+
|
|
666
714
|
# @param name [Symbol, String] band key (`sub`, `low`, `mid`, `high`)
|
|
667
715
|
# @return [Hash] source descriptor for a frequency band
|
|
668
716
|
def frequency_band(name)
|
|
669
717
|
mapping_source(:frequency_band, band: name.to_sym)
|
|
670
718
|
end
|
|
671
719
|
|
|
720
|
+
# @return [Hash] source descriptor for a held frequency-band peak
|
|
721
|
+
def frequency_band_peak(name)
|
|
722
|
+
mapping_source(:frequency_band_peak, band: name.to_sym)
|
|
723
|
+
end
|
|
724
|
+
|
|
672
725
|
# @return [Hash] source descriptor for the sub-bass frequency band
|
|
673
726
|
def sub
|
|
674
727
|
frequency_band(:sub)
|
|
675
728
|
end
|
|
676
729
|
|
|
730
|
+
# @return [Hash] source descriptor for the held sub-bass peak
|
|
731
|
+
def sub_peak
|
|
732
|
+
frequency_band_peak(:sub)
|
|
733
|
+
end
|
|
734
|
+
|
|
677
735
|
# @return [Hash] source descriptor for the low/bass frequency band
|
|
678
736
|
def low
|
|
679
737
|
frequency_band(:low)
|
|
680
738
|
end
|
|
681
739
|
|
|
740
|
+
# @return [Hash] source descriptor for the held low/bass peak
|
|
741
|
+
def low_peak
|
|
742
|
+
frequency_band_peak(:low)
|
|
743
|
+
end
|
|
744
|
+
|
|
682
745
|
# @return [Hash] source descriptor for the low/bass frequency band
|
|
683
746
|
def bass
|
|
684
747
|
frequency_band(:low)
|
|
685
748
|
end
|
|
686
749
|
|
|
750
|
+
# @return [Hash] source descriptor for the held low/bass peak
|
|
751
|
+
def bass_peak
|
|
752
|
+
frequency_band_peak(:low)
|
|
753
|
+
end
|
|
754
|
+
|
|
687
755
|
# @return [Hash] source descriptor for the mid frequency band
|
|
688
756
|
def mid
|
|
689
757
|
frequency_band(:mid)
|
|
690
758
|
end
|
|
691
759
|
|
|
760
|
+
# @return [Hash] source descriptor for the held mid peak
|
|
761
|
+
def mid_peak
|
|
762
|
+
frequency_band_peak(:mid)
|
|
763
|
+
end
|
|
764
|
+
|
|
692
765
|
# @return [Hash] source descriptor for the high frequency band
|
|
693
766
|
def high
|
|
694
767
|
frequency_band(:high)
|
|
695
768
|
end
|
|
696
769
|
|
|
770
|
+
# @return [Hash] source descriptor for the held high peak
|
|
771
|
+
def high_peak
|
|
772
|
+
frequency_band_peak(:high)
|
|
773
|
+
end
|
|
774
|
+
|
|
697
775
|
# @return [Hash] source descriptor for the high/treble frequency band
|
|
698
776
|
def treble
|
|
699
777
|
frequency_band(:high)
|
|
700
778
|
end
|
|
701
779
|
|
|
780
|
+
# @return [Hash] source descriptor for the held high/treble peak
|
|
781
|
+
def treble_peak
|
|
782
|
+
frequency_band_peak(:high)
|
|
783
|
+
end
|
|
784
|
+
|
|
702
785
|
# @return [Hash] source descriptor for FFT spectrum array
|
|
703
786
|
def fft_spectrum
|
|
704
787
|
mapping_source(:fft_spectrum)
|
|
@@ -757,13 +840,138 @@ module Vizcore
|
|
|
757
840
|
mapping_source(:beat_count)
|
|
758
841
|
end
|
|
759
842
|
|
|
843
|
+
# @return [Hash] source descriptor for 0.0..1.0 phase within the current beat
|
|
844
|
+
def beat_phase
|
|
845
|
+
mapping_source(:beat_phase)
|
|
846
|
+
end
|
|
847
|
+
|
|
848
|
+
# @return [Hash] source descriptor for half-beat subdivision pulses
|
|
849
|
+
def beat_2
|
|
850
|
+
mapping_source(:beat_2)
|
|
851
|
+
end
|
|
852
|
+
|
|
853
|
+
# @return [Hash] source descriptor for quarter-beat subdivision pulses
|
|
854
|
+
def beat_4
|
|
855
|
+
mapping_source(:beat_4)
|
|
856
|
+
end
|
|
857
|
+
|
|
858
|
+
# @return [Hash] source descriptor for eighth-beat subdivision pulses
|
|
859
|
+
def beat_8
|
|
860
|
+
mapping_source(:beat_8)
|
|
861
|
+
end
|
|
862
|
+
|
|
863
|
+
# @return [Hash] source descriptor for triplet subdivision pulses
|
|
864
|
+
def beat_triplet
|
|
865
|
+
mapping_source(:beat_triplet)
|
|
866
|
+
end
|
|
867
|
+
|
|
868
|
+
# @return [Hash] source descriptor for triplet subdivision pulses
|
|
869
|
+
def triplet
|
|
870
|
+
mapping_source(:beat_triplet)
|
|
871
|
+
end
|
|
872
|
+
|
|
873
|
+
# @return [Hash] source descriptor for 0.0..1.0 phase within the current 4-beat bar
|
|
874
|
+
def bar_phase
|
|
875
|
+
mapping_source(:bar_phase)
|
|
876
|
+
end
|
|
877
|
+
|
|
878
|
+
# @return [Hash] source descriptor for completed 4-beat bars
|
|
879
|
+
def bar_count(value = NO_ARGUMENT)
|
|
880
|
+
return @params[:bar_count] = Integer(value) unless value.equal?(NO_ARGUMENT)
|
|
881
|
+
|
|
882
|
+
mapping_source(:bar_count)
|
|
883
|
+
end
|
|
884
|
+
|
|
885
|
+
# @return [Hash] source descriptor for completed 8-bar phrases
|
|
886
|
+
def phrase_count
|
|
887
|
+
mapping_source(:phrase_count)
|
|
888
|
+
end
|
|
889
|
+
|
|
760
890
|
# @return [Hash] source descriptor for estimated BPM
|
|
761
891
|
def bpm
|
|
762
892
|
mapping_source(:bpm)
|
|
763
893
|
end
|
|
764
894
|
|
|
895
|
+
# @return [Hash] source descriptor for tempo estimator confidence
|
|
896
|
+
def bpm_confidence
|
|
897
|
+
mapping_source(:bpm_confidence)
|
|
898
|
+
end
|
|
899
|
+
|
|
900
|
+
# @return [Hash] source descriptor for spectral centroid in Hz
|
|
901
|
+
def spectral_centroid
|
|
902
|
+
mapping_source(:spectral_centroid)
|
|
903
|
+
end
|
|
904
|
+
|
|
905
|
+
# @return [Hash] source descriptor for spectral rolloff in Hz
|
|
906
|
+
def spectral_rolloff
|
|
907
|
+
mapping_source(:spectral_rolloff)
|
|
908
|
+
end
|
|
909
|
+
|
|
910
|
+
# @return [Hash] source descriptor for spectral flatness
|
|
911
|
+
def spectral_flatness
|
|
912
|
+
mapping_source(:spectral_flatness)
|
|
913
|
+
end
|
|
914
|
+
|
|
915
|
+
# @return [Hash] source descriptor for positive spectrum delta
|
|
916
|
+
def spectral_flux
|
|
917
|
+
mapping_source(:spectral_flux)
|
|
918
|
+
end
|
|
919
|
+
|
|
920
|
+
# @return [Hash] source descriptor for time-domain zero crossing rate
|
|
921
|
+
def zero_crossing_rate
|
|
922
|
+
mapping_source(:zero_crossing_rate)
|
|
923
|
+
end
|
|
924
|
+
|
|
925
|
+
# @param name [Symbol, String] runtime global value name
|
|
926
|
+
# @return [Hash] source descriptor for mutable runtime globals
|
|
927
|
+
def global(name)
|
|
928
|
+
mapping_source(:global, name: name.to_sym)
|
|
929
|
+
end
|
|
930
|
+
|
|
931
|
+
# @param wave [Symbol, String] one of `sine`, `triangle`, `saw`, `square`
|
|
932
|
+
# @param rate [Numeric] cycles per second
|
|
933
|
+
# @param phase [Numeric] phase offset in cycles
|
|
934
|
+
# @return [Hash] source descriptor for a time-based low-frequency oscillator
|
|
935
|
+
def lfo(wave = :sine, rate: 1.0, phase: 0.0)
|
|
936
|
+
mapping_source(:lfo, wave: wave.to_sym, rate: Float(rate), phase: Float(phase))
|
|
937
|
+
rescue ArgumentError, TypeError
|
|
938
|
+
raise ArgumentError, "lfo rate and phase must be numeric"
|
|
939
|
+
end
|
|
940
|
+
|
|
941
|
+
# @param source [Symbol, Hash] source to trigger the envelope
|
|
942
|
+
# @param attack [Numeric] seconds from 0.0 to peak
|
|
943
|
+
# @param decay [Numeric] seconds from peak to sustain
|
|
944
|
+
# @param sustain [Numeric] sustain gain once decay is complete (0.0..1.0)
|
|
945
|
+
# @param release [Numeric] seconds from sustain to 0.0
|
|
946
|
+
# @param threshold [Numeric] value threshold that starts attack
|
|
947
|
+
# @param peak [Numeric] peak gain multiplier
|
|
948
|
+
# @return [Hash] source descriptor for an ADSR envelope
|
|
949
|
+
def adsr(source = :kick, attack: 0.02, decay: 0.08, sustain: 0.7, release: 0.16, threshold: 0.0, peak: 1.0)
|
|
950
|
+
source_value = source.nil? ? { kind: :kick } : normalize_source(source)
|
|
951
|
+
mapping_source(
|
|
952
|
+
:adsr,
|
|
953
|
+
source: source_value,
|
|
954
|
+
attack: normalize_non_negative_float(attack, :attack),
|
|
955
|
+
decay: normalize_non_negative_float(decay, :decay),
|
|
956
|
+
sustain: clamp(normalize_float(sustain, :sustain), 0.0, 1.0),
|
|
957
|
+
release: normalize_non_negative_float(release, :release),
|
|
958
|
+
threshold: normalize_float(threshold, :threshold),
|
|
959
|
+
peak: normalize_float(peak, :peak)
|
|
960
|
+
)
|
|
961
|
+
end
|
|
962
|
+
|
|
963
|
+
# Alias for ADSR envelope mapping source.
|
|
964
|
+
#
|
|
965
|
+
# @param source [Symbol, Hash] source to trigger the envelope
|
|
966
|
+
# @param options [Numeric] envelope timing and shaping values
|
|
967
|
+
# @return [Hash] source descriptor for a general envelope
|
|
968
|
+
def envelope(source = :kick, **options)
|
|
969
|
+
adsr(source, **options)
|
|
970
|
+
end
|
|
971
|
+
|
|
765
972
|
# @return [Hash] serialized layer payload
|
|
766
973
|
def to_h
|
|
974
|
+
validate_strict_params! if @strict
|
|
767
975
|
layer = {
|
|
768
976
|
name: @name,
|
|
769
977
|
type: resolved_type,
|
|
@@ -1322,16 +1530,7 @@ module Vizcore
|
|
|
1322
1530
|
end
|
|
1323
1531
|
|
|
1324
1532
|
def deep_dup(value)
|
|
1325
|
-
|
|
1326
|
-
when Hash
|
|
1327
|
-
value.each_with_object({}) do |(key, entry), output|
|
|
1328
|
-
output[key] = deep_dup(entry)
|
|
1329
|
-
end
|
|
1330
|
-
when Array
|
|
1331
|
-
value.map { |entry| deep_dup(entry) }
|
|
1332
|
-
else
|
|
1333
|
-
value
|
|
1334
|
-
end
|
|
1533
|
+
Vizcore::DeepCopy.copy(value)
|
|
1335
1534
|
end
|
|
1336
1535
|
|
|
1337
1536
|
def evaluate_transform_block(initial_options, &block)
|
|
@@ -1365,19 +1564,26 @@ module Vizcore
|
|
|
1365
1564
|
raise ArgumentError, "param min must be less than or equal to max"
|
|
1366
1565
|
end
|
|
1367
1566
|
|
|
1368
|
-
def normalize_transform(gain: nil, range: nil, min: nil, max: nil, curve: nil, attack: nil, release: nil, deadzone: nil)
|
|
1567
|
+
def normalize_transform(gain: nil, range: nil, min: nil, max: nil, curve: nil, attack: nil, release: nil, deadzone: nil, threshold: nil, hysteresis: nil, hold: nil, decay: nil, cooldown: nil, one_shot: nil, as: nil)
|
|
1369
1568
|
range_min, range_max = normalize_range(range, context: "mapping")
|
|
1370
1569
|
min = range_min if min.nil?
|
|
1371
1570
|
max = range_max if max.nil?
|
|
1372
1571
|
|
|
1373
1572
|
output = {}
|
|
1374
1573
|
output[:deadzone] = normalize_non_negative_float(deadzone, :deadzone) unless deadzone.nil?
|
|
1574
|
+
output[:as] = normalize_mapping_mode(as) unless as.nil?
|
|
1575
|
+
output[:threshold] = normalize_float(threshold, :threshold) unless threshold.nil?
|
|
1576
|
+
output[:hysteresis] = normalize_non_negative_float(hysteresis, :hysteresis) unless hysteresis.nil?
|
|
1375
1577
|
output[:gain] = normalize_float(gain, :gain) unless gain.nil?
|
|
1376
1578
|
output[:min] = normalize_float(min, :min) unless min.nil?
|
|
1377
1579
|
output[:max] = normalize_float(max, :max) unless max.nil?
|
|
1378
1580
|
output[:curve] = normalize_curve(curve) unless curve.nil?
|
|
1379
1581
|
output[:attack] = clamp(normalize_float(attack, :attack), 0.0, 1.0) unless attack.nil?
|
|
1380
1582
|
output[:release] = clamp(normalize_float(release, :release), 0.0, 1.0) unless release.nil?
|
|
1583
|
+
output[:hold] = normalize_non_negative_float(hold, :hold) unless hold.nil?
|
|
1584
|
+
output[:decay] = clamp(normalize_float(decay, :decay), 0.0, 1.0) unless decay.nil?
|
|
1585
|
+
output[:cooldown] = normalize_non_negative_float(cooldown, :cooldown) unless cooldown.nil?
|
|
1586
|
+
output[:one_shot] = !!one_shot unless one_shot.nil?
|
|
1381
1587
|
output
|
|
1382
1588
|
end
|
|
1383
1589
|
|
|
@@ -1419,11 +1625,30 @@ module Vizcore
|
|
|
1419
1625
|
|
|
1420
1626
|
def normalize_curve(value)
|
|
1421
1627
|
curve = value.to_sym
|
|
1422
|
-
return curve if %i[linear sqrt square ease_out].include?(curve)
|
|
1628
|
+
return curve if %i[linear sqrt square ease_out ease_in ease_in_out smoothstep exp log step].include?(curve)
|
|
1423
1629
|
|
|
1424
1630
|
raise ArgumentError, "unsupported mapping curve: #{value.inspect}"
|
|
1425
1631
|
end
|
|
1426
1632
|
|
|
1633
|
+
def normalize_mapping_mode(value)
|
|
1634
|
+
mode = value.to_sym
|
|
1635
|
+
return mode if %i[continuous trigger].include?(mode)
|
|
1636
|
+
|
|
1637
|
+
raise ArgumentError, "unsupported mapping mode: #{value.inspect}"
|
|
1638
|
+
end
|
|
1639
|
+
|
|
1640
|
+
def validate_strict_params!
|
|
1641
|
+
unknown = @params.keys.map(&:to_sym) - strict_allowed_params
|
|
1642
|
+
return if unknown.empty?
|
|
1643
|
+
|
|
1644
|
+
raise ArgumentError, "layer #{@name} has unknown params in strict mode: #{unknown.sort.join(', ')}"
|
|
1645
|
+
end
|
|
1646
|
+
|
|
1647
|
+
def strict_allowed_params
|
|
1648
|
+
catalog_params = Vizcore::LayerCatalog.params_for(resolved_type).keys
|
|
1649
|
+
(catalog_params + @param_schema.keys + STRICT_PARAM_ALLOWLIST).map(&:to_sym).uniq
|
|
1650
|
+
end
|
|
1651
|
+
|
|
1427
1652
|
def clamp(value, min, max)
|
|
1428
1653
|
[[value, min].max, max].min
|
|
1429
1654
|
end
|
|
@@ -1434,6 +1659,44 @@ module Vizcore
|
|
|
1434
1659
|
**options
|
|
1435
1660
|
}
|
|
1436
1661
|
end
|
|
1662
|
+
|
|
1663
|
+
def shape_id_suggestions(key)
|
|
1664
|
+
return "" if @shape_index_by_id.empty?
|
|
1665
|
+
|
|
1666
|
+
candidates = @shape_index_by_id.keys
|
|
1667
|
+
.map do |shape_id|
|
|
1668
|
+
[shape_id, levenshtein_distance(shape_id.to_s, key.to_s)]
|
|
1669
|
+
end
|
|
1670
|
+
.select { |_, distance| distance <= 3 }
|
|
1671
|
+
.sort_by { |shape_id, distance| [distance, shape_id.to_s] }
|
|
1672
|
+
.first(3)
|
|
1673
|
+
|
|
1674
|
+
return "" if candidates.empty?
|
|
1675
|
+
|
|
1676
|
+
candidates.map { |shape_id, _| shape_id.inspect }.join(", ")
|
|
1677
|
+
end
|
|
1678
|
+
|
|
1679
|
+
def levenshtein_distance(a, b)
|
|
1680
|
+
prev = (0..b.length).to_a
|
|
1681
|
+
b_chars = b.bytes
|
|
1682
|
+
a_bytes = a.bytes
|
|
1683
|
+
|
|
1684
|
+
a_bytes.each_with_index do |codepoint_a, index_a|
|
|
1685
|
+
current = [index_a + 1]
|
|
1686
|
+
b_chars.each_with_index do |codepoint_b, index_b|
|
|
1687
|
+
cost = codepoint_a == codepoint_b ? 0 : 1
|
|
1688
|
+
current << [
|
|
1689
|
+
current[index_b] + 1,
|
|
1690
|
+
prev[index_b + 1] + 1,
|
|
1691
|
+
prev[index_b] + cost
|
|
1692
|
+
].min
|
|
1693
|
+
end
|
|
1694
|
+
|
|
1695
|
+
prev = current
|
|
1696
|
+
end
|
|
1697
|
+
|
|
1698
|
+
prev[b.length]
|
|
1699
|
+
end
|
|
1437
1700
|
end
|
|
1438
1701
|
end
|
|
1439
1702
|
end
|
|
@@ -1,17 +1,24 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative "layer_builder"
|
|
4
|
+
require_relative "color_helpers"
|
|
4
5
|
|
|
5
6
|
module Vizcore
|
|
6
7
|
module DSL
|
|
7
8
|
# Collects related layers and applies shared layer parameters.
|
|
8
9
|
class LayerGroupBuilder
|
|
10
|
+
include ColorHelpers
|
|
11
|
+
|
|
9
12
|
# @param name [Symbol, String] group identifier stored on nested layer params
|
|
10
13
|
# @param styles [Hash] reusable layer parameter styles
|
|
14
|
+
# @param mapping_presets [Hash] reusable layer mapping presets
|
|
11
15
|
# @param defaults [Hash] scene defaults already applied before group params
|
|
12
|
-
|
|
16
|
+
# @param strict [Boolean] true when unknown layer params should fail
|
|
17
|
+
def initialize(name:, styles: {}, defaults: {}, mapping_presets: {}, strict: false)
|
|
13
18
|
@name = name.to_sym
|
|
14
19
|
@styles = styles
|
|
20
|
+
@mapping_presets = mapping_presets
|
|
21
|
+
@strict = !!strict
|
|
15
22
|
@params = deep_dup(defaults)
|
|
16
23
|
@layers = []
|
|
17
24
|
end
|
|
@@ -31,7 +38,7 @@ module Vizcore
|
|
|
31
38
|
# @yield Layer definition block
|
|
32
39
|
# @return [void]
|
|
33
40
|
def layer(name, &block)
|
|
34
|
-
builder = LayerBuilder.new(name: name, styles: @styles, defaults: layer_defaults)
|
|
41
|
+
builder = LayerBuilder.new(name: name, styles: @styles, mapping_presets: @mapping_presets, defaults: layer_defaults, strict: @strict)
|
|
35
42
|
builder.evaluate(&block)
|
|
36
43
|
@layers << builder.to_h
|
|
37
44
|
end
|
|
@@ -96,16 +103,7 @@ module Vizcore
|
|
|
96
103
|
end
|
|
97
104
|
|
|
98
105
|
def deep_dup(value)
|
|
99
|
-
|
|
100
|
-
when Hash
|
|
101
|
-
value.each_with_object({}) do |(key, entry), output|
|
|
102
|
-
output[key] = deep_dup(entry)
|
|
103
|
-
end
|
|
104
|
-
when Array
|
|
105
|
-
value.map { |entry| deep_dup(entry) }
|
|
106
|
-
else
|
|
107
|
-
value
|
|
108
|
-
end
|
|
106
|
+
Vizcore::DeepCopy.copy(value)
|
|
109
107
|
end
|
|
110
108
|
end
|
|
111
109
|
end
|