;; Display.eeform — a screen that isn't a Spectrum: a picture EELisp draws, byte by byte.
;;
;; The screen control evaluates :frame :hz times a second — 35 here, Doom's tic rate — and paints
;; the frame that comes back. This one is an "indexed" frame (docs/DISPLAY.md): 160×100 bytes, each
;; an entry in a 256-colour palette, written a column at a time — a whole column of one colour is
;; a single bfill, which is how a raycaster would draw a wall.
;;
;; In each frame the program has:
;;   ui-held  the PC keys held down, by KeyboardEvent.code — {"ArrowLeft" true "KeyW" true}
;;   ui-ms    milliseconds since the screen started: the clock
;;
;; Click the screen, then move the square with the arrow keys or W A S D. The stripes move on
;; their own, from ui-ms. Hold Space for a note whose pitch follows the square up and down: its
;; samples are made here, a square wave of bytes, and go with the frame as "audio".

(form "Display" :size (512 384)
  (label lblHelp :text "Click the screen, then use the arrow keys or W A S D; hold Space for a note." :at (16 12) :size (480 24))
  (screen scrDemo :frame "(display-frame ui-held ui-ms)" :hz 35 :at (16 44) :size (480 324)))

(def display-w 160)
(def display-h 100)
(def display-horizon 60)
;; The picture, a column after another: pixel (x, y) is byte x*h + y.
(def display-fb (make-bytes (* display-w display-h)))
(def display-x 72)
(def display-y 70)

;; 0–63 sky blues, 64–127 browns for the ground, 254 white, 255 yellow — three bytes a colour.
(def display-palette
  (bytes->base64
    (list->bytes
      (loop (i 0 acc '())
        (if (= i 256)
            acc
            (recur (+ i 1)
                   (append acc
                     (cond (< i 64)  (list i (+ 40 (* i 2)) (+ 120 (* i 2)))
                           (< i 128) (list (+ 60 (* (- i 64) 2)) (+ 40 (- i 64)) 20)
                           (= i 255) (list 255 220 0)
                           true      (list 255 255 255)))))))))

(defn display-held? (held a b) (or (dict-get held a) (dict-get held b)))

;; ── Sound: unsigned 8-bit samples, 128 silence, 22050 a second ───────────────────────────────
(def display-rate 22050)
(def display-pcm (make-bytes 4096))
(def display-last nil)   ; ui-ms of the frame before: this frame makes the samples since
(def display-ph 0.0)     ; how far into the current half of the wave the last frame stopped
(def display-lvl 0)

;; n samples of a square wave at hz, picking up where the last frame left off so it doesn't
;; click; a run of one level is one bfill.
(defn display-note (n hz)
  (let (half (/ display-rate (* 2.0 hz)))
    (loop (i 0 ph display-ph lvl display-lvl)
      (if (>= i n)
          (do (set! display-ph ph) (set! display-lvl lvl) (bytes->base64 display-pcm 0 n))
          (let (k (min (- n i) (max 1 (floor (- half ph)))))
            (do (bfill display-pcm i k (if (= lvl 1) 168 88))
                (if (>= (+ ph k) half)
                    (recur (+ i k) (- (+ ph k) half) (- 1 lvl))
                    (recur (+ i k) (+ ph k) lvl))))))))

;; One frame: move the square by the keys held, draw sky and ground a column at a time, then the
;; square, and hand the bytes over.
(defn display-frame (held ms)
  (do
    (let (dx (- (if (display-held? held "ArrowRight" "KeyD") 2 0) (if (display-held? held "ArrowLeft" "KeyA") 2 0))
          dy (- (if (display-held? held "ArrowDown" "KeyS") 2 0) (if (display-held? held "ArrowUp" "KeyW") 2 0)))
      (do (set! display-x (max 0 (min (- display-w 16) (+ display-x dx))))
          (set! display-y (max 0 (min (- display-h 16) (+ display-y dy))))))
    (let (shift (floor (/ ms 40)))
      (loop (x 0)
        (when (< x display-w)
          (do (bfill display-fb (* x display-h) display-horizon (mod (+ x shift) 64))
              (bfill display-fb (+ (* x display-h) display-horizon) (- display-h display-horizon)
                     (+ 64 (floor (/ (* x 63) display-w))))
              (recur (+ x 1))))))
    (loop (c 0)
      (when (< c 16)
        (do (bfill display-fb (+ (* (+ display-x c) display-h) display-y) 16 255)
            (recur (+ c 1)))))
    (let (n (if (nil? display-last) 0 (min 4096 (floor (* display-rate (/ (- ms display-last) 1000.0))))))
      (do (set! display-last ms)
          {"kind" "indexed" "w" display-w "h" display-h "order" "columns"
           "pixels" (bytes->base64 display-fb) "palette" display-palette "border" 0
           ;; higher up, higher: 220 Hz at the bottom to about 880 at the top
           "audio" (when (and (dict-get held "Space") (> n 0))
                     {"rate" display-rate "pcm" (display-note n (+ 220 (* 8 (- display-h 16 display-y))))})}))))
