]> git.sesse.net Git - ultimatescore/blob - nageru/ultimate.lua
Find the location of the HTML file from the location of the Nageru theme.
[ultimatescore] / nageru / ultimate.lua
1 -- Nageru theme for TFK mini-tournament 2017, based on the default theme.
2
3 local transition_start = -2.0
4 local transition_end = -1.0
5 local transition_type = 0
6 local transition_src_signal = 0
7 local transition_dst_signal = 0
8
9 local neutral_colors = {
10         {0.5, 0.5, 0.5},  -- Input 0.
11         {0.5, 0.5, 0.5},  -- Input 1.
12         {0.5, 0.5, 0.5},  -- Input 2.
13         {0.5, 0.5, 0.5},  -- Input 3.
14         {0.5, 0.5, 0.5}   -- Input 4.
15 }
16
17 local overlay_transition_start = -2.0
18 local overlay_transition_end = -1.0
19 local overlay_alpha_src = 0.0
20 local overlay_alpha_dst = 1.0
21 local overlay_enabled = false
22
23 local live_signal_num = 0
24 local preview_signal_num = 1
25 local NUM_CAMERAS = 5  -- Remember to update neutral_colors, too.
26
27 -- Valid values for live_signal_num and preview_signal_num.
28 local INPUT0_SIGNAL_NUM = 0
29 local INPUT1_SIGNAL_NUM = 1
30 local INPUT2_SIGNAL_NUM = 2
31 local INPUT3_SIGNAL_NUM = 3
32 local INPUT4_SIGNAL_NUM = 4
33 local STATIC_SIGNAL_NUM = NUM_CAMERAS
34
35 -- Preview-only signal showing the current signal with the overlay.
36 -- Not valid for live_signal_num!
37 local OVERLAY_SIGNAL_NUM = NUM_CAMERAS + 1
38
39 -- Valid values for transition_type. (Cuts are done directly, so they need no entry.)
40 local NO_TRANSITION = 0
41 local FADE_TRANSITION = 2
42
43 -- Last width/height/frame rate for each channel, if we have it.
44 -- Note that unlike the values we get from Nageru, the resolution is per
45 -- frame and not per field, since we deinterlace.
46 local last_resolution = {}
47
48 local cef_path = Nageru.THEME_PATH:match("(.*)/nageru/")
49 local cef_input = HTMLInput.new("file://" .. cef_path .. "/score.html")
50 cef_input:execute_javascript_async("play()")
51
52 function reload_cef()
53         cef_input:reload()
54         cef_input:execute_javascript_async("play()")
55 end
56
57 -- Utility function to help creating many similar chains that can differ
58 -- in a free set of chosen parameters.
59 function make_cartesian_product(parms, callback)
60         return make_cartesian_product_internal(parms, callback, 1, {})
61 end
62
63 function make_cartesian_product_internal(parms, callback, index, args)
64         if index > #parms then
65                 return callback(unpack(args))
66         end
67         local ret = {}
68         for _, value in ipairs(parms[index]) do
69                 args[index] = value
70                 ret[value] = make_cartesian_product_internal(parms, callback, index + 1, args)
71         end
72         return ret
73 end
74
75 -- An overlay with variable alpha.
76 function make_overlay(chain, base)
77         local image = chain:add_html_input(cef_input)
78         local multiply_effect = chain:add_effect(MultiplyEffect.new())
79         local overlay_effect = chain:add_effect(OverlayEffect.new(), base, multiply_effect)
80         return {
81                 image = image,
82                 multiply_effect = multiply_effect,
83                 overlay_effect = overlay_effect
84         }
85 end
86
87 function possibly_make_overlay(has_overlay, chain, base)
88         if has_overlay == true then
89                 return make_overlay(chain, base)
90         else
91                 return nil
92         end
93 end
94
95 function make_fade_input(chain, signal, live, deint, scale)
96         local input, wb_effect, resample_effect, last
97         if live then
98                 input = chain:add_live_input(false, deint)
99                 input:connect_signal(signal)
100                 last = input
101         else
102                 input = chain:add_effect(ImageInput.new("tfk_pause.png"))
103                 last = input
104         end
105
106         -- If we cared about this for the non-main inputs, we would have
107         -- checked hq here and invoked ResizeEffect instead.
108         if scale then
109                 resample_effect = chain:add_effect(ResampleEffect.new())
110                 last = resample_effect
111         end
112
113         -- Make sure to put the white balance after the scaling (usually more efficient).
114         if live then
115                 wb_effect = chain:add_effect(WhiteBalanceEffect.new())
116                 last = wb_effect
117         end
118
119         return {
120                 input = input,
121                 wb_effect = wb_effect,
122                 resample_effect = resample_effect,
123                 last = last
124         }
125 end
126
127 -- A chain to fade between two inputs, of which either can be a picture
128 -- or a live input. In practice only used live, but we still support the
129 -- hq parameter.
130 function make_fade_chain(input0_live, input0_deint, input0_scale, input1_live, input1_deint, input1_scale, has_overlay, hq)
131         local chain = EffectChain.new(16, 9)
132
133         local input0 = make_fade_input(chain, INPUT0_SIGNAL_NUM, input0_live, input0_deint, input0_scale)
134         local input1 = make_fade_input(chain, INPUT1_SIGNAL_NUM, input1_live, input1_deint, input1_scale)
135
136         -- If fading between two live inputs, the overlay is put on top.
137         -- If fading between the static picture and a live input,
138         -- the overlay is put on the live input.
139         local overlay = nil
140         if input0_live and not input1_live then
141                 overlay = possibly_make_overlay(has_overlay, chain, input0.last)
142                 if overlay then
143                         input0.last = overlay.overlay_effect
144                 end
145         elseif input1_live and not input0_live then
146                 overlay = possibly_make_overlay(has_overlay, chain, input1.last)
147                 if overlay then
148                         input1.last = overlay.overlay_effect
149                 end
150         end
151
152         local mix_effect = chain:add_effect(MixEffect.new(), input0.last, input1.last)
153         if input0_live and input1_live then
154                 overlay = possibly_make_overlay(has_overlay, chain, mix_effect)
155         end
156
157         chain:finalize(hq)
158
159         return {
160                 chain = chain,
161                 input0 = input0,
162                 input1 = input1,
163                 mix_effect = mix_effect,
164                 overlay = overlay
165         }
166 end
167
168 -- Chains to fade between two inputs, in various configurations.
169 local fade_chains = make_cartesian_product({
170         {"static", "live", "livedeint"},  -- input0_type
171         {true, false},                    -- input0_scale
172         {"static", "live", "livedeint"},  -- input1_type
173         {true, false},                    -- input1_scale
174         {true, false},                    -- has_overlay
175         {true}                            -- hq
176 }, function(input0_type, input0_scale, input1_type, input1_scale, has_overlay, hq)
177         local input0_live = (input0_type ~= "static")
178         local input1_live = (input1_type ~= "static")
179         local input0_deint = (input0_type == "livedeint")
180         local input1_deint = (input1_type == "livedeint")
181         return make_fade_chain(input0_live, input0_deint, input0_scale, input1_live, input1_deint, input1_scale, has_overlay, hq)
182 end)
183
184 -- A chain to show a single input on screen.
185 function make_simple_chain(input_deint, input_scale, has_overlay, hq)
186         local chain = EffectChain.new(16, 9)
187
188         local input = chain:add_live_input(false, input_deint)
189         input:connect_signal(0)  -- First input card. Can be changed whenever you want.
190
191         local resample_effect, resize_effect
192         if scale then
193                 if hq then
194                         resample_effect = chain:add_effect(ResampleEffect.new())
195                 else
196                         resize_effect = chain:add_effect(ResizeEffect.new())
197                 end
198         end
199
200         local wb_effect = chain:add_effect(WhiteBalanceEffect.new())
201         local overlay = possibly_make_overlay(has_overlay, chain, wb_effect)
202
203         chain:finalize(hq)
204
205         return {
206                 chain = chain,
207                 input = input,
208                 wb_effect = wb_effect,
209                 resample_effect = resample_effect,
210                 resize_effect = resize_effect,
211                 overlay = overlay
212         }
213 end
214
215 -- Make all possible combinations of single-input chains.
216 local simple_chains = make_cartesian_product({
217         {"live", "livedeint"},  -- input_type
218         {true, false},          -- input_scale
219         {true, false},          -- has_overlay
220         {true, false}           -- hq
221 }, function(input_type, input_scale, has_overlay, hq)
222         local input_deint = (input_type == "livedeint")
223         return make_simple_chain(input_deint, input_scale, has_overlay, hq)
224 end)
225
226 -- A chain to show a single static picture on screen. Never with CasparCG overlay.
227 local static_chains = make_cartesian_product({
228         {true, false}            -- hq
229 }, function(hq)
230         local chain = EffectChain.new(16, 9)
231         local chain_input = chain:add_effect(ImageInput.new("tfk_pause.png"))
232
233         chain:finalize(hq)
234         return {
235                 chain = chain
236         }
237 end)
238
239 -- A chain to show the overlay and nothing more. LQ only,
240 -- since it is not a valid live signal.
241 local overlay_chain_lq = EffectChain.new(16, 9)
242 local overlay_chain_lq_input = overlay_chain_lq:add_html_input(cef_input)
243 overlay_chain_lq:finalize(false)
244
245 -- Used for indexing into the tables of chains.
246 function get_input_type(signals, signal_num)
247         if signal_num == STATIC_SIGNAL_NUM then
248                 return "static"
249         elseif signals:get_interlaced(signal_num) then
250                 return "livedeint"
251         else
252                 return "live"
253         end
254 end
255
256 function needs_scale(signals, signal_num, width, height)
257         if signal_num == STATIC_SIGNAL_NUM then
258                 -- We assume this is already correctly scaled at load time.
259                 return false
260         end
261         assert(is_plain_signal(signal_num))
262         return (signals:get_width(signal_num) ~= width or signals:get_height(signal_num) ~= height)
263 end
264
265 function set_scale_parameters_if_needed(chain_or_input, width, height)
266         if chain_or_input.resample_effect then
267                 chain_or_input.resample_effect:set_int("width", width)
268                 chain_or_input.resample_effect:set_int("height", height)
269         elseif chain_or_input.resize_effect then
270                 chain_or_input.resize_effect:set_int("width", width)
271                 chain_or_input.resize_effect:set_int("height", height)
272         end
273 end
274
275 -- API ENTRY POINT
276 -- Returns the number of outputs in addition to the live (0) and preview (1).
277 -- Called only once, at the start of the program.
278 function num_channels()
279         return NUM_CAMERAS + 2  -- static picture and overlay
280 end
281
282 function is_plain_signal(num)
283         return num >= INPUT0_SIGNAL_NUM and num <= INPUT4_SIGNAL_NUM
284 end
285
286 -- Helper function to write e.g. “720p60”. The difference between this
287 -- and get_channel_resolution_raw() is that this one also can say that
288 -- there's no signal.
289 function get_channel_resolution(signal_num)
290         res = last_resolution[signal_num]
291         if (not res) or not res.is_connected then
292                 return "disconnected"
293         end
294         if res.height <= 0 then
295                 return "no signal"
296         end
297         if not res.has_signal then
298                 if res.height == 525 then
299                         -- Special mode for the USB3 cards.
300                         return "no signal"
301                 end
302                 return get_channel_resolution_raw(res) .. ", no signal"
303         else
304                 return get_channel_resolution_raw(res)
305         end
306 end
307
308 -- Helper function to write e.g. “60” or “59.94”.
309 function get_frame_rate(res)
310         local nom = res.frame_rate_nom
311         local den = res.frame_rate_den
312         if nom % den == 0 then
313                 return nom / den
314         else
315                 return string.format("%.2f", nom / den)
316         end
317 end
318
319 -- Helper function to write e.g. “720p60”.
320 function get_channel_resolution_raw(res)
321         if res.interlaced then
322                 return res.height .. "i" .. get_frame_rate(res)
323         else
324                 return res.height .. "p" .. get_frame_rate(res)
325         end
326 end
327
328 -- API ENTRY POINT
329 -- Returns the name for each additional channel (starting from 2).
330 -- Called at the start of the program, and then each frame for live
331 -- channels in case they change resolution.
332 function channel_name(channel)
333         local signal_num = channel - 2
334         if signal_num == INPUT0_SIGNAL_NUM then
335                 return "Main (" .. get_channel_resolution(signal_num) .. ")"
336         elseif signal_num == INPUT1_SIGNAL_NUM then
337                 return "Secondary (" .. get_channel_resolution(signal_num) .. ")"
338         elseif signal_num == INPUT2_SIGNAL_NUM then
339                 return "Goal L (" .. get_channel_resolution(signal_num) .. ")"
340         elseif signal_num == INPUT3_SIGNAL_NUM then
341                 return "Goal R (" .. get_channel_resolution(signal_num) .. ")"
342         elseif signal_num == INPUT4_SIGNAL_NUM then
343                 return "Ambience (" .. get_channel_resolution(signal_num) .. ")"
344         elseif signal_num == STATIC_SIGNAL_NUM then
345                 return "Static picture"
346         elseif signal_num == OVERLAY_SIGNAL_NUM then
347                 return "Overlay"
348         end
349 end
350
351 -- API ENTRY POINT
352 -- Returns, given a channel number, which signal it corresponds to (starting from 0).
353 -- Should return -1 if the channel does not correspond to a simple signal.
354 -- (The information is used for whether right-click on the channel should bring up
355 -- an input selector or not.)
356 -- Called once for each channel, at the start of the program.
357 -- Will never be called for live (0) or preview (1).
358 function channel_signal(channel)
359         if is_plain_signal(channel - 2) then
360                 return channel - 2
361         else
362                 return -1
363         end
364 end
365
366 -- API ENTRY POINT
367 -- Called every frame. Returns the color (if any) to paint around the given
368 -- channel. Returns a CSS color (typically to mark live and preview signals);
369 -- "transparent" is allowed.
370 -- Will never be called for live (0) or preview (1).
371 function channel_color(channel)
372         if transition_type ~= NO_TRANSITION then
373                 if channel_involved_in(channel, transition_src_signal) or
374                    channel_involved_in(channel, transition_dst_signal) then
375                         return "#f00"
376                 end
377         else
378                 if channel_involved_in(channel, live_signal_num) then
379                         return "#f00"
380                 end
381         end
382         if channel_involved_in(channel, preview_signal_num) then
383                 return "#0f0"
384         end
385         return "transparent"
386 end
387
388 function channel_involved_in(channel, signal_num)
389         if is_plain_signal(signal_num) then
390                 return channel == (signal_num + 2)
391         end
392         if signal_num == STATIC_SIGNAL_NUM then
393                 return (channel == NUM_CAMERAS)
394         end
395         return false
396 end
397
398 -- API ENTRY POINT
399 -- Returns if a given channel supports setting white balance (starting from 2).
400 -- Called only once for each channel, at the start of the program.
401 function supports_set_wb(channel)
402         return is_plain_signal(channel - 2)
403 end
404
405 -- API ENTRY POINT
406 -- Gets called with a new gray point when the white balance is changing.
407 -- The color is in linear light (not sRGB gamma).
408 function set_wb(channel, red, green, blue)
409         if is_plain_signal(channel - 2) then
410                 neutral_colors[channel - 2 + 1] = { red, green, blue }
411         end
412 end
413
414 function finish_transitions(t)
415         if transition_type ~= NO_TRANSITION and t >= transition_end then
416                 live_signal_num = transition_dst_signal
417                 transition_type = NO_TRANSITION
418         end
419
420         -- Disable the overlay if it is no longer visible.
421         if overlay_enabled and t > overlay_transition_end and overlay_alpha_dst == 0.0 then
422                 overlay_enabled = false
423                 print("Turning off overlay")
424         end
425 end
426
427 function in_transition(t)
428         return t >= transition_start and t <= transition_end
429 end
430
431 -- API ENTRY POINT
432 -- Called every frame.
433 function get_transitions(t)
434         if preview_signal_num == OVERLAY_SIGNAL_NUM then
435                 if t < overlay_transition_end then
436                         -- Fade in progress.
437                         return {}
438                 end
439                 if overlay_enabled then
440                         return {"Overlay off", "", "Fade ovl out"}
441                 else
442                         return {"Overlay on", "", "Fade ovl in"}
443                 end
444         end
445
446         if in_transition(t) then
447                 -- Transition already in progress, the only thing we can do is really
448                 -- cut to the preview. (TODO: Make an “abort” and/or “finish”, too?)
449                 return {"Cut"}
450         end
451
452         if live_signal_num == preview_signal_num then
453                 -- No transitions possible.
454                 return {}
455         end
456
457         if (is_plain_signal(live_signal_num) or live_signal_num == STATIC_SIGNAL_NUM) and
458            (is_plain_signal(preview_signal_num) or preview_signal_num == STATIC_SIGNAL_NUM) then
459                 return {"Cut", "", "Fade"}
460         end
461
462         return {"Cut"}
463 end
464
465 function swap_preview_live()
466         local temp = live_signal_num
467         live_signal_num = preview_signal_num
468         preview_signal_num = temp
469 end
470
471 function start_transition(type_, t, duration)
472         transition_start = t
473         transition_end = t + duration
474         transition_type = type_
475         transition_src_signal = live_signal_num
476         transition_dst_signal = preview_signal_num
477         swap_preview_live()
478 end
479
480 -- API ENTRY POINT
481 -- Called when the user clicks a transition button.
482 function transition_clicked(num, t)
483         if preview_signal_num == OVERLAY_SIGNAL_NUM then
484                 if num == 0 then
485                         -- Cut.
486                         overlay_transition_start = -2.0
487                         overlay_transition_end = -1.0
488                         if overlay_enabled then
489                                 overlay_enabled = false
490                                 overlay_alpha_src = 0.0
491                                 overlay_alpha_dst = 0.0
492                         else
493                                 overlay_enabled = true
494                                 overlay_alpha_src = 1.0
495                                 overlay_alpha_dst = 1.0
496                         end
497                 elseif num == 2 then
498                         -- Fade.
499                         overlay_transition_start = t
500                         overlay_transition_end = t + 1.0
501                         if overlay_enabled then
502                                 overlay_alpha_src = 1.0
503                                 overlay_alpha_dst = 0.0
504                         else
505                                 overlay_alpha_src = 0.0
506                                 overlay_alpha_dst = 1.0
507                         end
508                         overlay_enabled = true
509                 end
510                 return
511         end
512
513         if num == 0 then
514                 -- Cut.
515                 if in_transition(t) then
516                         -- Ongoing transition; finish it immediately before the cut.
517                         finish_transitions(transition_end)
518                 end
519
520                 swap_preview_live()
521         elseif num == 1 then
522                 -- Zoom.
523                 finish_transitions(t)
524
525                 if live_signal_num == preview_signal_num then
526                         -- Nothing to do.
527                         return
528                 end
529
530                 if is_plain_signal(live_signal_num) and is_plain_signal(preview_signal_num) then
531                         -- We can't zoom between these. Just make a cut.
532                         io.write("Cutting from " .. live_signal_num .. " to " .. live_signal_num .. "\n")
533                         swap_preview_live()
534                         return
535                 end
536         elseif num == 2 then
537                 finish_transitions(t)
538
539                 -- Fade.
540                 if (live_signal_num ~= preview_signal_num) and
541                    (is_plain_signal(live_signal_num) or
542                     live_signal_num == STATIC_SIGNAL_NUM) and
543                    (is_plain_signal(preview_signal_num) or
544                     preview_signal_num == STATIC_SIGNAL_NUM) then
545                         start_transition(FADE_TRANSITION, t, 1.0)
546                 else
547                         -- Fades involving SBS are ignored (we have no chain for it).
548                 end
549         end
550 end
551
552 -- API ENTRY POINT
553 function channel_clicked(num)
554         preview_signal_num = num
555 end
556
557 function get_fade_chain(signals, t, width, height, input_resolution)
558         local input0_type = get_input_type(signals, transition_src_signal)
559         local input0_scale = needs_scale(signals, transition_src_signal, width, height)
560         local input1_type = get_input_type(signals, transition_dst_signal)
561         local input1_scale = needs_scale(signals, transition_dst_signal, width, height)
562         local chain = fade_chains[input0_type][input0_scale][input1_type][input1_scale][overlay_enabled][true]
563         prepare = function()
564                 if input0_type == "live" or input0_type == "livedeint" then
565                         chain.input0.input:connect_signal(transition_src_signal)
566                         set_neutral_color_from_signal(chain.input0.wb_effect, transition_src_signal)
567                 end
568                 set_scale_parameters_if_needed(chain.input0, width, height)
569                 if input1_type == "live" or input1_type == "livedeint" then
570                         chain.input1.input:connect_signal(transition_dst_signal)
571                         set_neutral_color_from_signal(chain.input1.wb_effect, transition_dst_signal)
572                 end
573                 set_scale_parameters_if_needed(chain.input1, width, height)
574                 local tt = calc_fade_progress(t, transition_start, transition_end)
575
576                 chain.mix_effect:set_float("strength_first", 1.0 - tt)
577                 chain.mix_effect:set_float("strength_second", tt)
578                 prepare_overlay_live(chain, t)
579         end
580         return chain.chain, prepare
581 end
582
583 -- API ENTRY POINT
584 -- Called every frame. Get the chain for displaying at input <num>,
585 -- where 0 is live, 1 is preview, 2 is the first channel to display
586 -- in the bottom bar, and so on up to num_channels()+1. t is the
587 -- current time in seconds. width and height are the dimensions of
588 -- the output, although you can ignore them if you don't need them
589 -- (they're useful if you want to e.g. know what to resample by).
590 --
591 -- <signals> is basically an exposed InputState, which you can use to
592 -- query for information about the signals at the point of the current
593 -- frame. In particular, you can call get_width() and get_height()
594 -- for any signal number, and use that to e.g. assist in chain selection.
595 --
596 -- You should return two objects; the chain itself, and then a
597 -- function (taking no parameters) that is run just before rendering.
598 -- The function needs to call connect_signal on any inputs, so that
599 -- it gets updated video data for the given frame. (You are allowed
600 -- to switch which input your input is getting from between frames,
601 -- but not calling connect_signal results in undefined behavior.)
602 -- If you want to change any parameters in the chain, this is also
603 -- the right place.
604 --
605 -- NOTE: The chain returned must be finalized with the Y'CbCr flag
606 -- if and only if num==0.
607 function get_chain(num, t, width, height, signals)
608         local input_resolution = {}
609         for signal_num=0,(NUM_CAMERAS-1) do
610                 local res = {
611                         width = signals:get_width(signal_num),
612                         height = signals:get_height(signal_num),
613                         interlaced = signals:get_interlaced(signal_num),
614                         has_signal = signals:get_has_signal(signal_num),
615                         is_connected = signals:get_is_connected(signal_num),
616                         frame_rate_nom = signals:get_frame_rate_nom(signal_num),
617                         frame_rate_den = signals:get_frame_rate_den(signal_num)
618                 }
619
620                 if res.interlaced then
621                         -- Convert height from frame height to field height.
622                         -- (Needed for e.g. place_rectangle.)
623                         res.height = res.height * 2
624
625                         -- Show field rate instead of frame rate; really for cosmetics only
626                         -- (and actually contrary to EBU recommendations, although in line
627                         -- with typical user expectations).
628                         res.frame_rate_nom = res.frame_rate_nom * 2
629                 end
630
631                 input_resolution[signal_num] = res
632         end
633         last_resolution = input_resolution
634
635         if num == 0 then  -- Live.
636                 -- See if we're in a transition.
637                 finish_transitions(t)
638                 if transition_type == FADE_TRANSITION then
639                         return get_fade_chain(signals, t, width, height, input_resolution)
640                 elseif is_plain_signal(live_signal_num) then
641                         local input_type = get_input_type(signals, live_signal_num)
642                         local input_scale = needs_scale(signals, live_signal_num, width, height)
643                         local chain = simple_chains[input_type][input_scale][overlay_enabled][true]
644                         prepare = function()
645                                 chain.input:connect_signal(live_signal_num)
646                                 set_scale_parameters_if_needed(chain, width, height)
647                                 set_neutral_color_from_signal(chain.wb_effect, live_signal_num)
648                                 prepare_overlay_live(chain, t)
649                         end
650                         return chain.chain, prepare
651                 elseif live_signal_num == STATIC_SIGNAL_NUM then  -- Static picture.
652                         local chain = static_chains[true]
653                         prepare = function()
654                                 prepare_overlay_live(chain, t)
655                         end
656                         return chain.chain, prepare
657                 else
658                         assert(false)
659                 end
660         end
661
662         -- We do not show overlays on the individual preview inputs.
663         -- The M/E preview matches what we'd put live by doing a transition, as always.
664         local show_overlay = false
665         if num == 1 then  -- Preview.
666                 if preview_signal_num == OVERLAY_SIGNAL_NUM then
667                         num = live_signal_num + 2
668                         show_overlay = not overlay_enabled
669
670                         if transition_type ~= NO_TRANSITION then
671                                 num = transition_dst_signal + 2
672                         end
673                 else
674                         num = preview_signal_num + 2
675                         show_overlay = overlay_enabled
676                 end
677         end
678
679         -- Individual preview inputs (usually without overlay).
680         if is_plain_signal(num - 2) then
681                 local signal_num = num - 2
682                 local input_type = get_input_type(signals, signal_num)
683                 local input_scale = needs_scale(signals, signal_num, width, height)
684                 local chain = simple_chains[input_type][input_scale][show_overlay][false]
685                 prepare = function()
686                         chain.input:connect_signal(signal_num)
687                         set_scale_parameters_if_needed(chain, width, height)
688                         set_neutral_color(chain.wb_effect, neutral_colors[signal_num + 1])
689                         prepare_overlay_static(chain, t)
690                 end
691                 return chain.chain, prepare
692         end
693         if num == STATIC_SIGNAL_NUM + 2 then
694                 local chain = static_chains[false]
695                 prepare = function()
696                         prepare_overlay_static(chain, t)
697                 end
698                 return chain.chain, prepare
699         end
700         if num == OVERLAY_SIGNAL_NUM + 2 then
701                 prepare = function()
702 --                      prepare_overlay(overlay_chain_lq, t)
703                 end
704                 return overlay_chain_lq, prepare
705         end
706 end
707
708 -- This is broken, of course (even for positive numbers), but Lua doesn't give us access to real rounding.
709 function round(x)
710         return math.floor(x + 0.5)
711 end
712
713 function prepare_overlay_live(chain, t)
714         if chain.overlay then
715                 local tt = calc_fade_progress(t, overlay_transition_start, overlay_transition_end)
716                 overlay_alpha = overlay_alpha_src + tt * (overlay_alpha_dst - overlay_alpha_src)
717                 --print("overlay_alpha=" .. overlay_alpha .. " [" .. overlay_alpha_src .. "," .. overlay_alpha_dst .. "]@" .. tt)
718                 if t > overlay_transition_end and overlay_alpha_dst == 0.0 then
719                         overlay_enabled = false  -- Takes effect next frame.
720         --              print("Turning off overlay")
721                 end
722                 chain.overlay.multiply_effect:set_vec4("factor", overlay_alpha, overlay_alpha, overlay_alpha, overlay_alpha)
723         end
724 end
725
726 function prepare_overlay_static(chain)
727         if chain.overlay then
728                 chain.overlay.multiply_effect:set_vec4("factor", 1.0, 1.0, 1.0, 1.0)
729         end
730 end
731
732 function set_neutral_color(effect, color)
733         effect:set_vec3("neutral_color", color[1], color[2], color[3])
734 end
735
736 function set_neutral_color_from_signal(effect, signal)
737         if is_plain_signal(signal) then
738                 set_neutral_color(effect, neutral_colors[signal - INPUT0_SIGNAL_NUM + 1])
739         end
740 end
741
742 function calc_zoom_progress(t)
743         if t < transition_start then
744                 return 0.0
745         elseif t > transition_end then
746                 return 1.0
747         else
748                 local tt = (t - transition_start) / (transition_end - transition_start)
749                 -- Smooth it a bit.
750                 return math.sin(tt * 3.14159265358 * 0.5)
751         end
752 end
753
754 function calc_fade_progress(t, transition_start, transition_end)
755         local tt = (t - transition_start) / (transition_end - transition_start)
756         if tt < 0.0 then
757                 return 0.0
758         elseif tt > 1.0 then
759                 return 1.0
760         end
761
762         -- Make the fade look maybe a tad more natural, by pumping it
763         -- through a sigmoid function.
764         tt = 10.0 * tt - 5.0
765         tt = 1.0 / (1.0 + math.exp(-tt))
766
767         return tt
768 end
769
770 ThemeMenu.set(
771         { "Reload overlay", reload_cef }
772 )