Class: Vizcore::Server::Runner

Inherits:
Object
  • Object
show all
Defined in:
lib/vizcore/server/runner.rb

Overview

Bootstraps Rack/Puma, audio pipeline, scene reload, and MIDI runtime.

Constant Summary collapse

DEFAULT_PROFILE_NAME =
"default".freeze

Instance Method Summary collapse

Constructor Details

#initialize(config, manifest: nil, initial_profile: nil, output: $stdout) ⇒ Runner

Returns a new instance of Runner.

Parameters:



24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
# File 'lib/vizcore/server/runner.rb', line 24

def initialize(config, manifest: nil, initial_profile: nil, output: $stdout)
  @config = config
  @manifest = manifest
  @available_profiles = derive_available_profiles
  @active_profile = normalize_profile_name(initial_profile)
  @active_scene_file = active_scene_file_for_profile(@active_profile)
  @output = output
  @shader_source_resolver = Vizcore::DSL::ShaderSourceResolver.new
  @scene_catalog_mutex = Mutex.new
  @scene_catalog = []
  @scene_watcher = nil
  @osc_schedule_mutex = Mutex.new
  @osc_schedule_threads = []
  @osc_runtime_active = false
  @midi_runtime = nil
  @osc_runtime = nil
  @broadcaster = nil
  @runtime_globals_mutex = Mutex.new
  @runtime_globals = {}
  @live_controls = {
    "blackout" => default_live_control_state,
    "freeze" => default_live_control_state
  }
end

Instance Method Details

#runvoid

This method returns an undefined value.

Run server lifecycle until interrupted.



54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
# File 'lib/vizcore/server/runner.rb', line 54

def run
  validate_scene_file!
  validate_public_bind_settings!
  validate_feature_settings!
  validate_control_preset_settings!
  validate_plugin_asset_settings!
  validate_audio_settings!
  definition = load_definition_for_profile(@active_profile)
  control_preset = load_control_preset
  timeline_entry = initial_timeline_entry(definition)
  scene = initial_scene(definition) || fallback_scene
  broadcaster = nil
  app = RackApp.new(
    frontend_root: Vizcore.frontend_root,
    audio_source: runtime_audio_source,
    audio_file: runtime_audio_file,
    scene_names: scene_names_for(definition),
    tap_tempo_key: @tap_tempo_key,
    key_mappings: key_mappings_for(definition),
    globals: runtime_globals_snapshot,
    control_preset: control_preset,
    control_preset_path: @config.control_preset,
    plugin_assets: @config.plugin_assets,
    projector_mode: @config.projector_mode,
    runtime_status_provider: -> { runtime_status_payload }
  )
  server = Puma::Server.new(app, nil, min_threads: 0, max_threads: 4)
  server.add_tcp_listener(@config.host, @config.port)
  server.run

  input_manager = build_input_manager
  warn_if_sample_rate_mismatch(input_manager)
  broadcaster = FrameBroadcaster.new(
    scene_name: scene[:name].to_s,
    scene_layers: scene[:layers],
    scene_catalog: definition[:scenes],
    transitions: definition[:transitions],
    initial_timeline_entry: timeline_entry,
    input_manager: input_manager,
    analysis_pipeline: replay_pipeline,
    noise_gate: @config.noise_gate,
    audio_normalize: audio_normalize_settings(definition),
    bpm: bpm_setting(definition),
    bpm_lock: bpm_lock_setting(definition),
    onset_sensitivity: analysis_setting(definition, :onset_sensitivity, 1.0),
    fft_preview_bins: analysis_setting(definition, :fft_bins, Vizcore::Analysis::Pipeline::DEFAULT_FFT_PREVIEW_BINS),
    peak_hold_frames: analysis_setting(definition, :peak_hold_frames, 0),
    silence_reset_frames: analysis_setting(definition, :silence_reset_frames, Vizcore::Analysis::Pipeline::SILENCE_RESET_FRAMES),
    error_reporter: ->(message) { @output.puts(message) }
  )
  @broadcaster = broadcaster
  configure_runtime_for_definition(definition: definition, broadcaster: broadcaster)
  replace_scene_catalog(definition[:scenes])
  if file_transport_enabled?
    broadcaster.sync_transport(playing: false, position_seconds: 0.0)
  end
  broadcaster.start
  register_client_message_handler(broadcaster)
  @midi_runtime = start_midi_runtime(definition, broadcaster)
  @osc_runtime = start_osc_runtime(broadcaster)
  @scene_watcher = start_scene_watcher(
    broadcaster,
    definition: definition,
    scene_file: active_scene_file
  ) do |reloaded_definition|
    @midi_runtime = refresh_midi_runtime(@midi_runtime, reloaded_definition, broadcaster)
  end if @config.reload?

  @output.puts("Vizcore server listening at http://#{@config.host}:#{@config.port}")
  @output.puts("Projector output: http://#{@config.host}:#{@config.port}/projector")
  @output.puts("Control panel: http://#{@config.host}:#{@config.port}/control")
  @output.puts("Scene: #{scene[:name]}")
  @output.puts("Hot reload: #{@config.reload? ? 'enabled' : 'disabled'}")
  @output.puts("Audio playback: http://#{@config.host}:#{@config.port}/audio-file") if file_transport_enabled?
  @output.puts("Feature replay: #{@config.feature_file}") if feature_replay?
  @output.puts("OSC sync: udp://#{@config.host}:#{@config.osc_port}") if @osc_runtime
  @output.puts("Press Ctrl+C to stop.")

  wait_for_interrupt
ensure
  Vizcore::Server::WebSocketHandler.clear_message_handler
  stop_osc_runtime(@osc_runtime)
  stop_midi_runtime(@midi_runtime)
  @scene_watcher&.stop
  broadcaster&.stop
  server&.stop(true)
end

#warn_if_sample_rate_mismatch(input_manager) ⇒ Object



142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
# File 'lib/vizcore/server/runner.rb', line 142

def warn_if_sample_rate_mismatch(input_manager)
  return unless input_manager.respond_to?(:status)

  status = input_manager.status
  return unless status[:sample_rate_mismatch]

  requested = status[:requested_sample_rate]
  actual = status[:sample_rate]
  return unless requested && actual

  @output.puts(
    "Warning: requested audio sample rate #{requested} does not match device sample rate #{actual}; " \
    "analysis will use #{actual}."
  )
end