package llama_interactive

  1. Overview
  2. Docs

Source file window.ml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
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
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
open Tsdl
module Signal = Llama.Signal
module Ctx = Signal.Ctx
module List = Llama.List

module Global = struct
  let initialized = ref false

  let init () =
    if not !initialized then
      match Sdl.init Sdl.Init.video with
      | Error (`Msg msg) ->
          Sdl.log "Error initializing sdl: %s" msg;
          exit 1
      | Ok () -> initialized := true
end

let f_01_to_byte f = Llama.Float.clamp_01 f *. 255.0 |> Float.to_int

let rgba_01_to_bytes (r, g, b, a) =
  (f_01_to_byte r, f_01_to_byte g, f_01_to_byte b, f_01_to_byte a)

module Rect_rgba = struct
  type t = { sdl_rect : Sdl.rect; rgb : int * int * int; a : int }
end

module Visualization_style = struct
  type t = {
    pixel_scale : int;
    sample_scale : float;
    sample_to_rgba_01 : float -> Types.rgba_01;
  }
end

module Sample_buffer = struct
  type t = { samples : float array; mutable next_i : int }

  let create size =
    let samples = Array.init size (Fun.const 0.0) in
    { samples; next_i = 0 }

  let length t = t.next_i
  let is_full t = t.next_i == Array.length t.samples

  let append_unless_full t sample =
    if not (is_full t) then (
      Array.set t.samples t.next_i sample;
      t.next_i <- t.next_i + 1)

  let clear t = t.next_i <- 0

  let first_positive_gradient_zero_cross_index t =
    let rec loop i =
      if i >= t.next_i then None
      else
        let prev = Array.get t.samples (i - 1) in
        let current = Array.get t.samples i in
        if prev <= 0.0 && current >= 0.0 then Some i else loop (i + 1)
    in
    loop 1

  let iteri t ~offset ~stride ~max_iterations ~f =
    let rec loop i count =
      if i >= t.next_i || count >= max_iterations then ()
      else (
        f count (Array.get t.samples i);
        loop (i + stride) (count + 1))
    in
    loop offset 0
end

module Visualization = struct
  type t = {
    style : Visualization_style.t;
    sample_buffer : Sample_buffer.t;
    sample_count_within_current_frame : int option ref;
    stable : bool;
    stride : int;
  }

  let sample_buffer_size = 2048

  let create ~style ~stable ~stride =
    {
      style;
      sample_buffer = Sample_buffer.create sample_buffer_size;
      sample_count_within_current_frame = ref None;
      stable;
      stride;
    }

  let iteri_samples t ~window_width ~f =
    let needed_num_samples = (window_width / t.stride) + 1 in
    let offset =
      if t.stable then
        match
          Sample_buffer.first_positive_gradient_zero_cross_index t.sample_buffer
        with
        | Some i ->
            let remaining_samples_if_we_start_at_i =
              (Sample_buffer.length t.sample_buffer - i) / t.stride
            in
            if remaining_samples_if_we_start_at_i >= needed_num_samples then i
            else 0
        | None -> 0
      else 0
    in
    Sample_buffer.iteri t.sample_buffer ~offset ~stride:t.stride
      ~max_iterations:needed_num_samples ~f

  let scaled_pixel_y_of_sample sample ~window_height ~pixel_scale ~sample_scale
      =
    let window_y_mid = Int.to_float (window_height / 2) in
    Float.to_int (window_y_mid -. (sample *. sample_scale *. window_y_mid))
    / pixel_scale

  let rect_rgba_drain_iter t ~window_size:(window_width, window_height) ~f =
    let scaled_pixel_y_of_sample =
      scaled_pixel_y_of_sample ~window_height ~pixel_scale:t.style.pixel_scale
        ~sample_scale:t.style.sample_scale
    in
    let window_y_mid = Int.to_float (window_height / 2) in
    let scaled_pixel_y_to_sample scaled_pixel_y =
      (window_y_mid
      -. (Int.to_float scaled_pixel_y *. Int.to_float t.style.pixel_scale))
      /. (t.style.sample_scale *. window_y_mid)
    in
    let mk_rect_rgba ~scaled_pixel_y ~i =
      let x = i * t.style.pixel_scale in
      let interpolated_sample = scaled_pixel_y_to_sample scaled_pixel_y in
      let r, g, b, a =
        rgba_01_to_bytes
          (t.style.sample_to_rgba_01
             (t.style.sample_scale *. interpolated_sample))
      in
      let sdl_rect =
        Sdl.Rect.create ~x
          ~y:(scaled_pixel_y * t.style.pixel_scale)
          ~w:t.style.pixel_scale ~h:t.style.pixel_scale
      in
      { Rect_rgba.sdl_rect; rgb = (r, g, b); a }
    in
    let prev_sample = ref None in
    iteri_samples t ~window_width ~f:(fun i sample ->
        let scaled_pixel_y = scaled_pixel_y_of_sample sample in
        f (mk_rect_rgba ~scaled_pixel_y ~i);
        (match !prev_sample with
        | None -> ()
        | Some prev_sample ->
            (* fill in the vertical space between the previous and current sample *)
            let scaled_pixel_y0 = scaled_pixel_y_of_sample prev_sample in
            let scaled_pixel_y1 = scaled_pixel_y_of_sample sample in
            let scaled_pixel_y_values =
              if scaled_pixel_y0 < scaled_pixel_y1 then
                List.init ~len:(scaled_pixel_y1 - scaled_pixel_y0) ~f:(fun i ->
                    scaled_pixel_y0 + i)
              else if scaled_pixel_y0 > scaled_pixel_y1 then
                List.init
                  ~len:(scaled_pixel_y0 - scaled_pixel_y1 + 1)
                  ~f:(fun i -> scaled_pixel_y1 + i)
              else []
            in
            List.iter scaled_pixel_y_values ~f:(fun scaled_pixel_y ->
                f (mk_rect_rgba ~scaled_pixel_y ~i)));
        prev_sample := Some sample);
    Sample_buffer.clear t.sample_buffer;
    t.sample_count_within_current_frame := None
end

let create_inputs () =
  let open Input in
  let keyboard = All_keyboard.init ~f:(fun () -> Signal.var false) in
  let keyboard_signals = All_keyboard.map keyboard ~f:fst in
  let keyboard_refs = All_keyboard.map keyboard ~f:snd in
  let mouse = Mouse_pos.init ~f:(fun () -> Signal.var 0.0) in
  let mouse_signals = Mouse_pos.map mouse ~f:fst in
  let mouse_refs = Mouse_pos.map mouse ~f:snd in
  let signals = { keyboard = keyboard_signals; mouse = mouse_signals } in
  let refs = { keyboard = keyboard_refs; mouse = mouse_refs } in
  (signals, refs)

let key_of_scancode (all_keyboard : 'a Input.All_keyboard.t)
    (scancode : Sdl.scancode) =
  match Sdl.Scancode.enum scancode with
  | `A -> Some all_keyboard.key_a
  | `B -> Some all_keyboard.key_b
  | `C -> Some all_keyboard.key_c
  | `D -> Some all_keyboard.key_d
  | `E -> Some all_keyboard.key_e
  | `F -> Some all_keyboard.key_f
  | `G -> Some all_keyboard.key_g
  | `H -> Some all_keyboard.key_h
  | `I -> Some all_keyboard.key_i
  | `J -> Some all_keyboard.key_j
  | `K -> Some all_keyboard.key_k
  | `L -> Some all_keyboard.key_l
  | `M -> Some all_keyboard.key_m
  | `N -> Some all_keyboard.key_n
  | `O -> Some all_keyboard.key_o
  | `P -> Some all_keyboard.key_p
  | `Q -> Some all_keyboard.key_q
  | `R -> Some all_keyboard.key_r
  | `S -> Some all_keyboard.key_s
  | `T -> Some all_keyboard.key_t
  | `U -> Some all_keyboard.key_u
  | `V -> Some all_keyboard.key_v
  | `W -> Some all_keyboard.key_w
  | `X -> Some all_keyboard.key_x
  | `Y -> Some all_keyboard.key_y
  | `Z -> Some all_keyboard.key_z
  | `Semicolon -> Some all_keyboard.key_semicolon
  | `Apostrophe -> Some all_keyboard.key_apostrophe
  | `Comma -> Some all_keyboard.key_comma
  | `Period -> Some all_keyboard.key_period
  | `Space -> Some all_keyboard.key_space
  | `K1 -> Some all_keyboard.key_1
  | `K2 -> Some all_keyboard.key_2
  | `K3 -> Some all_keyboard.key_3
  | `K4 -> Some all_keyboard.key_4
  | `K5 -> Some all_keyboard.key_5
  | `K6 -> Some all_keyboard.key_6
  | `K7 -> Some all_keyboard.key_7
  | `K8 -> Some all_keyboard.key_8
  | `K9 -> Some all_keyboard.key_9
  | `K0 -> Some all_keyboard.key_0
  | _ -> None

type t = {
  window : Sdl.window;
  renderer : Sdl.renderer;
  fps : float;
  background_rgba_01 : Types.rgba_01;
  visualization : Visualization.t option ref;
  input_signals : (bool Signal.t, float Signal.t) Input.t;
  input_refs : (bool ref, float ref) Input.t;
}

let proc_event t event =
  let typ = Sdl.Event.get event Sdl.Event.typ in
  if typ == Sdl.Event.quit then (
    Sdl.quit ();
    exit 0)
  else if typ == Sdl.Event.key_down then
    let scancode = Sdl.Event.get event Sdl.Event.keyboard_scancode in
    match key_of_scancode t.input_refs.keyboard scancode with
    | Some key_ref -> key_ref := true
    | None -> ()
  else if typ == Sdl.Event.key_up then
    let scancode = Sdl.Event.get event Sdl.Event.keyboard_scancode in
    match key_of_scancode t.input_refs.keyboard scancode with
    | Some key_ref -> key_ref := false
    | None -> ()
  else if typ == Sdl.Event.mouse_motion then (
    let mm_x = Sdl.Event.get event Sdl.Event.mouse_motion_x in
    let mm_y = Sdl.Event.get event Sdl.Event.mouse_motion_y in
    let window_width, window_height = Sdl.get_window_size t.window in
    let mouse_x_01 = Float.of_int mm_x /. Float.of_int window_width in
    let mouse_y_01 = Float.of_int mm_y /. Float.of_int window_height in
    t.input_refs.mouse.mouse_x := mouse_x_01;
    t.input_refs.mouse.mouse_y := mouse_y_01)

let rec drain_events t =
  let event = Sdl.Event.create () in
  if Sdl.poll_event (Some event) then (
    proc_event t event;
    drain_events t)
  else ()

let create ~title ~width ~height ~fps ~background_rgba_01 =
  Global.init ();
  match
    Sdl.create_window_and_renderer ~w:width ~h:height Sdl.Window.windowed
  with
  | Error (`Msg msg) ->
      Sdl.log "Error creating window: %s" msg;
      exit 1
  | Ok (window, renderer) ->
      let input_signals, input_refs = create_inputs () in
      Sdl.set_window_title window title;
      {
        window;
        renderer;
        fps;
        background_rgba_01;
        visualization = ref None;
        input_signals;
        input_refs;
      }

let log_error = function Ok () -> () | Error (`Msg msg) -> Sdl.log "%s" msg

let render t =
  let r, g, b, a = rgba_01_to_bytes t.background_rgba_01 in
  Sdl.set_render_draw_color t.renderer r g b a |> log_error;
  Sdl.render_clear t.renderer |> log_error;
  (match !(t.visualization) with
  | None -> ()
  | Some visualization ->
      Visualization.rect_rgba_drain_iter visualization
        ~window_size:(Sdl.get_window_size t.window)
        ~f:(fun { Rect_rgba.sdl_rect; rgb = r, g, b; a } ->
          Sdl.set_render_draw_color t.renderer r g b a |> log_error;
          Sdl.render_fill_rect t.renderer (Some sdl_rect) |> log_error));
  Sdl.render_present t.renderer

let visualize t ?(pixel_scale = Defaults.pixel_scale)
    ?(sample_scale = Defaults.sample_scale)
    ?(sample_to_rgba_01 = Defaults.sample_to_rgb_01) ?(stride = Defaults.stride)
    ?(stable = Defaults.stable) signal =
  let style =
    { Visualization_style.pixel_scale; sample_scale; sample_to_rgba_01 }
  in
  let visualization = Visualization.create ~style ~stable ~stride in
  t.visualization := Some visualization;
  Signal.of_raw (fun (ctx : Ctx.t) ->
      let sample = Signal.sample signal ctx in
      Sample_buffer.append_unless_full visualization.sample_buffer sample;
      sample)

let rec main_loop t =
  let open Lwt.Syntax in
  drain_events t;
  render t;
  let* () = Lwt_unix.sleep (1.0 /. t.fps) in
  main_loop t

let with_lwt ?(title = Defaults.title) ?(width = Defaults.width)
    ?(height = Defaults.height) ?(fps = Defaults.fps)
    ?(background_rgba_01 = Defaults.background_rgba_01)
    ?(f_delay_s = Defaults.f_delay_s) f =
  let open Lwt.Syntax in
  let t = create ~title ~width ~height ~fps ~background_rgba_01 in
  let f_lwt =
    (* Wait before running the user function to give the window time to open
       and get ready. [f] is probably going to start doing realtime things such as
       run oscillators and opening the window can consume a lot of cpu which can
       interrupt the oscillation and lead to audible artifacts. *)
    let* () = Lwt_unix.sleep f_delay_s in
    f t
  in
  let main_loop_lwt = main_loop t in
  let+ _, f_out = Lwt.both main_loop_lwt f_lwt in
  f_out

let with_ ?(title = Defaults.title) ?(width = Defaults.width)
    ?(height = Defaults.height) ?(fps = Defaults.fps)
    ?(background_rgba_01 = Defaults.background_rgba_01)
    ?(f_delay_s = Defaults.f_delay_s) f =
  Lwt_main.run
    (with_lwt ~title ~width ~height ~fps ~background_rgba_01 ~f_delay_s f)

let input_signals t = t.input_signals
OCaml

Innovation. Community. Security.