Monorepo for Aesthetic.Computer aesthetic.computer

feat: Common Lisp notepat — keyboard instrument boots on bare metal

Port notepat core from JS to Common Lisp for AC Native OS:
- Full QWERTY keyboard → chromatic note mapping (2.5 octaves)
- 5 wave types (sine/triangle/sawtooth/square/noise) with Tab cycling
- 32-voice polyphonic ALSA synth with stereo panning
- ALSA mixer auto-unmute + volume max (fixes silent audio)
- Per-note chromatic rainbow colors with trail effects
- Background color lerps to match active notes
- 6x10 bitmap font rendering for status bar
- Optimized fb-copy-scaled using memcpy (was per-pixel loops)
- Pixel scale targets ~200px wide (bigger pixels)
- Octave control (1-9 / arrows), ESC triple-press quit
- Progress log at fedac/native/cl/PROGRESS.md

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

+772 -103
+107
fedac/native/cl/PROGRESS.md
··· 1 + # AC Native OS — Common Lisp Edition Progress 2 + 3 + ## Overview 4 + 5 + A bare-metal Common Lisp OS runtime for AC Native, using SBCL + CFFI. Boots directly to notepat (musical keyboard instrument) on real ThinkPad hardware via UEFI. 6 + 7 + ## Architecture 8 + 9 + - **Runtime**: SBCL 2.6.2 compiled to standalone binary (~12MB) 10 + - **Graphics**: DRM/KMS framebuffer with pixel scaling (targets ~200px wide) 11 + - **Audio**: ALSA PCM with 32-voice polyphonic synth (48kHz stereo S16LE) 12 + - **Input**: evdev keyboard polling (full QWERTY) 13 + - **Build**: Docker reproducible build (Fedora 43 + GCC 15 + Linux 6.19.9) 14 + - **Boot**: UEFI → Linux kernel with embedded initramfs → SBCL binary as /init child 15 + 16 + ## What Works (2026-03-22) 17 + 18 + ### Core Runtime 19 + - [x] DRM display init (card0/card1/card2 probe + fbdev fallback) 20 + - [x] Double-buffered dumb buffers with page flip 21 + - [x] Pixel scaling with optimized memcpy-based fb-copy-scaled 22 + - [x] 60fps frame sync via clock_nanosleep 23 + - [x] evdev input polling (keyboard) 24 + - [x] ALSA audio output with mixer unmute + volume max 25 + - [x] 5 oscillator types: sine, square, triangle, sawtooth, noise 26 + - [x] Polyphonic synth (32 voices, per-voice pan/attack/decay/duration) 27 + - [x] Audio thread with lock-based voice management 28 + 29 + ### Graphics 30 + - [x] Immediate-mode primitives: wipe, ink, plot, line, box, circle 31 + - [x] Alpha blending (per-pixel ARGB32) 32 + - [x] 6x10 bitmap font rendering with scaling 33 + - [x] Color system (make-color, pack-argb32, blend) 34 + 35 + ### Notepat Instrument 36 + - [x] QWERTY keyboard → chromatic note mapping (2.5 octaves per layout) 37 + - [x] Key down → synth voice, key up → kill voice 38 + - [x] Per-note chromatic rainbow colors 39 + - [x] Background color lerps to average of active notes 40 + - [x] Trail effect: fading colored bars per note+octave on release 41 + - [x] Active note indicators (bright full-width bars) 42 + - [x] Octave control (1-9 keys, arrow up/down) 43 + - [x] Wave type cycling (Tab: sine → triangle → sawtooth → square → noise) 44 + - [x] Confirmation blip sound on wave switch 45 + - [x] Stereo panning (lower notes left, higher notes right) 46 + - [x] Status bar: wave type, octave, FPS counter 47 + - [x] ESC triple-press to quit 48 + - [x] Power button to quit 49 + 50 + ### Build System 51 + - [x] Docker reproducible build (Dockerfile.builder) 52 + - [x] AC_BUILD_LISP=1 flag swaps CL binary into initramfs 53 + - [x] Oven OTA pipeline compatible 54 + - [x] USB flash via privileged Docker container 55 + 56 + ## What's Next 57 + 58 + ### Short Term 59 + - [ ] Audio: sample recording/playback (microphone capture + sample bank) 60 + - [ ] Audio: echo/room effect 61 + - [ ] Audio: pitch shift (trackpad or slider) 62 + - [ ] Input: touch/trackpad support 63 + - [ ] Display: boot animation / splash screen 64 + - [ ] System: USB log writing for debug 65 + - [ ] System: config.json reading (handle, credentials) 66 + 67 + ### Medium Term 68 + - [ ] WiFi management (wpa_supplicant integration) 69 + - [ ] OTA self-update 70 + - [ ] Metronome (clock-synced) 71 + - [ ] Text-to-speech (flite integration) 72 + - [ ] Multiple pieces with piece switching (prompt → notepat) 73 + - [ ] Touch grid UI for note pads 74 + 75 + ### Long Term 76 + - [ ] KidLisp evaluator in CL (replace QuickJS) 77 + - [ ] Network/WebSocket for multiplayer 78 + - [ ] Full piece API surface (matching JS disk.mjs) 79 + 80 + ## File Map 81 + 82 + | File | Purpose | 83 + |------|---------| 84 + | `main.lisp` | Entry point, notepat instrument loop | 85 + | `packages.lisp` | All package/export definitions | 86 + | `drm-display.lisp` | DRM/KMS init, dumb buffers, page flip, present | 87 + | `drm-constants.lisp` | DRM ioctl numbers and struct defs | 88 + | `framebuffer.lisp` | Pixel buffer alloc, clear, plot, blend, scaled copy | 89 + | `graph.lisp` | Immediate-mode 2D: wipe, ink, plot, line, box, circle | 90 + | `color.lisp` | ARGB32 color struct, packing, alpha blend | 91 + | `font-data.lisp` | 6x10 BDF bitmap font (95 ASCII chars) | 92 + | `font.lisp` | Font rendering and measurement | 93 + | `audio.lisp` | ALSA init, mixer unmute, voice management, audio thread | 94 + | `audio-synth.lisp` | Oscillators (sine/square/tri/saw/noise), voice struct | 95 + | `alsa-bindings.lisp` | CFFI bindings for libasound | 96 + | `input.lisp` | evdev device scanning, event polling | 97 + | `input-keycodes.lisp` | Linux keycode constants (full QWERTY) | 98 + | `syscalls.lisp` | CFFI wrappers for Linux syscalls | 99 + | `util.lisp` | Logging, monotonic clock, frame sync | 100 + | `config.lisp` | USB config.json reader | 101 + | `ac-native.asd` | ASDF system definition | 102 + | `build.lisp` | SBCL save-lisp-and-die build script | 103 + 104 + ## Hardware Tested 105 + 106 + - ThinkPad X1 Nano Gen 2 (Intel i915) 107 + - ThinkPad Yoga 11e Gen 5 (Intel i915)
+74 -8
fedac/native/cl/audio.lisp
··· 14 14 (next-id 1 :type fixnum) 15 15 (lock (bordeaux-threads:make-lock "audio"))) 16 16 17 + (defun unmute-all-mixers (card-name) 18 + "Open the ALSA mixer for CARD-NAME, unmute all playback switches, set volumes to max." 19 + (ac-native.util:ac-log "[audio] unmuting mixer: ~A~%" card-name) 20 + (cffi:with-foreign-object (mixer-ptr :pointer) 21 + (when (zerop (cffi:foreign-funcall "snd_mixer_open" 22 + :pointer mixer-ptr :int 0 :int)) 23 + (let ((mixer (cffi:mem-ref mixer-ptr :pointer))) 24 + (cffi:foreign-funcall "snd_mixer_attach" 25 + :pointer mixer :string card-name :int) 26 + (cffi:foreign-funcall "snd_mixer_selem_register" 27 + :pointer mixer :pointer (cffi:null-pointer) :pointer (cffi:null-pointer) :int) 28 + (cffi:foreign-funcall "snd_mixer_load" :pointer mixer :int) 29 + ;; Iterate all elements 30 + (loop for elem = (cffi:foreign-funcall "snd_mixer_first_elem" 31 + :pointer mixer :pointer) 32 + then (cffi:foreign-funcall "snd_mixer_elem_next" 33 + :pointer elem :pointer) 34 + until (cffi:null-pointer-p elem) do 35 + (let ((name (cffi:foreign-funcall "snd_mixer_selem_get_name" 36 + :pointer elem :string))) 37 + (when (not (zerop (cffi:foreign-funcall "snd_mixer_selem_is_active" 38 + :pointer elem :int))) 39 + ;; Unmute playback switch 40 + (when (not (zerop (cffi:foreign-funcall "snd_mixer_selem_has_playback_switch" 41 + :pointer elem :int))) 42 + (cffi:foreign-funcall "snd_mixer_selem_set_playback_switch_all" 43 + :pointer elem :int 1 :int) 44 + (ac-native.util:ac-log "[audio] unmuted: ~A~%" name)) 45 + ;; Set volume to max 46 + (when (not (zerop (cffi:foreign-funcall "snd_mixer_selem_has_playback_volume" 47 + :pointer elem :int))) 48 + (cffi:with-foreign-objects ((minv :long) (maxv :long)) 49 + (cffi:foreign-funcall "snd_mixer_selem_get_playback_volume_range" 50 + :pointer elem :pointer minv :pointer maxv :int) 51 + (let ((mx (cffi:mem-ref maxv :long))) 52 + (cffi:foreign-funcall "snd_mixer_selem_set_playback_volume_all" 53 + :pointer elem :long mx :int) 54 + (ac-native.util:ac-log "[audio] volume ~A: ~D~%" name mx))))))) 55 + (cffi:foreign-funcall "snd_mixer_close" :pointer mixer :int))))) 56 + 17 57 (defun audio-init () 18 58 "Initialize ALSA audio output and start the mixing thread." 19 - (multiple-value-bind (pcm err) (ac-native.alsa:pcm-open "default") 59 + ;; Try multiple devices with retries (HDA codec may not be probed yet) 60 + (let ((devices '("hw:0,0" "hw:1,0" "hw:0,1" "hw:1,1" 61 + "plughw:0,0" "plughw:1,0" "default")) 62 + (pcm nil) 63 + (card-idx 0)) 64 + (loop for attempt from 0 below 5 65 + until pcm do 66 + (when (> attempt 0) 67 + (ac-native.util:ac-log "[audio] retry ~D/4 — waiting 2s...~%" attempt) 68 + (sleep 2)) 69 + (dolist (dev devices) 70 + (multiple-value-bind (handle err) (ac-native.alsa:pcm-open dev) 71 + (when handle 72 + (ac-native.util:ac-log "[audio] opened: ~A (attempt ~D)~%" dev attempt) 73 + (setf pcm handle) 74 + ;; Extract card index from device name 75 + (let ((pos (position #\: dev))) 76 + (when pos 77 + (let ((ch (char dev (1+ pos)))) 78 + (when (digit-char-p ch) 79 + (setf card-idx (- (char-code ch) (char-code #\0))))))) 80 + (return)) 81 + (when (= attempt 0) 82 + (ac-native.util:ac-log "[audio] ~A: ~A~%" dev 83 + (ac-native.alsa:snd-strerror err)))))) 84 + 20 85 (unless pcm 21 - (ac-native.util:ac-log "[audio] failed to open: ~A~%" 22 - (ac-native.alsa:snd-strerror err)) 23 - ;; Try hw:0,0 24 - (multiple-value-setq (pcm err) (ac-native.alsa:pcm-open "hw:0,0")) 25 - (unless pcm 26 - (ac-native.util:ac-log "[audio] hw:0,0 also failed~%") 27 - (return-from audio-init nil))) 86 + (ac-native.util:ac-log "[audio] no ALSA device found~%") 87 + (return-from audio-init nil)) 28 88 29 89 ;; Set params: S16_LE, stereo, 48kHz, 50ms latency 30 90 (let ((ret (ac-native.alsa:pcm-set-params ··· 37 97 (ac-native.alsa:snd-strerror ret)) 38 98 (ac-native.alsa:pcm-close pcm) 39 99 (return-from audio-init nil))) 100 + 101 + ;; Unmute all mixer controls and set volumes to max 102 + (handler-case 103 + (unmute-all-mixers (format nil "hw:~D" card-idx)) 104 + (error (e) 105 + (ac-native.util:ac-log "[audio] mixer error: ~A~%" e))) 40 106 41 107 (let ((audio (make-ac-audio :pcm pcm :running t))) 42 108 ;; Init voice slots
+105 -5
fedac/native/cl/font-data.lisp
··· 1 - ;;; Font bitmap data — placeholder, will be generated from C headers 1 + ;;; Font bitmap data — 6x10 BDF font (Misc-Fixed-Medium-R-Normal--10) 2 + ;;; 95 printable ASCII chars (32-126), each 10 rows, 6px wide (MSB-first in byte) 2 3 3 4 (in-package :ac-native.font) 4 5 5 - ;; 8x8 font — 95 printable ASCII characters (space through ~) 6 - ;; Each character is 8 bytes (8 rows of 8 pixels) 7 - ;; TODO: port from fedac/native/src/font-8x8.h 8 - (defvar *font-8x8* (make-array (* 95 8) :element-type '(unsigned-byte 8) :initial-element 0)) 6 + (defconstant +font-w+ 6) 7 + (defconstant +font-h+ 10) 8 + 9 + (defvar *font-6x10* 10 + (make-array '(95 10) 11 + :element-type '(unsigned-byte 8) 12 + :initial-contents 13 + '((0 0 0 0 0 0 0 0 0 0) ; SPC (32) 14 + (0 32 32 32 32 32 0 32 0 0) ; ! (33) 15 + (0 80 80 80 0 0 0 0 0 0) ; " (34) 16 + (0 80 80 248 80 248 80 80 0 0) ; # (35) 17 + (0 32 112 160 112 40 112 32 0 0) ; $ (36) 18 + (0 72 168 80 32 80 168 144 0 0) ; % (37) 19 + (0 64 160 160 64 168 144 104 0 0) ; & (38) 20 + (0 32 32 32 0 0 0 0 0 0) ; ' (39) 21 + (0 16 32 64 64 64 32 16 0 0) ; ( (40) 22 + (0 64 32 16 16 16 32 64 0 0) ; ) (41) 23 + (0 0 136 80 248 80 136 0 0 0) ; * (42) 24 + (0 0 32 32 248 32 32 0 0 0) ; + (43) 25 + (0 0 0 0 0 0 48 32 64 0) ; , (44) 26 + (0 0 0 0 248 0 0 0 0 0) ; - (45) 27 + (0 0 0 0 0 0 32 112 32 0) ; . (46) 28 + (0 8 8 16 32 64 128 128 0 0) ; / (47) 29 + (0 32 80 136 136 136 80 32 0 0) ; 0 (48) 30 + (0 32 96 160 32 32 32 248 0 0) ; 1 (49) 31 + (0 112 136 8 48 64 128 248 0 0) ; 2 (50) 32 + (0 248 8 16 48 8 136 112 0 0) ; 3 (51) 33 + (0 16 48 80 144 248 16 16 0 0) ; 4 (52) 34 + (0 248 128 176 200 8 136 112 0 0) ; 5 (53) 35 + (0 48 64 128 176 200 136 112 0 0) ; 6 (54) 36 + (0 248 8 16 16 32 64 64 0 0) ; 7 (55) 37 + (0 112 136 136 112 136 136 112 0 0) ; 8 (56) 38 + (0 112 136 152 104 8 16 96 0 0) ; 9 (57) 39 + (0 0 32 112 32 0 32 112 32 0) ; : (58) 40 + (0 0 32 112 32 0 48 32 64 0) ; (59) 41 + (0 8 16 32 64 32 16 8 0 0) ; < (60) 42 + (0 0 0 248 0 248 0 0 0 0) ; = (61) 43 + (0 64 32 16 8 16 32 64 0 0) ; > (62) 44 + (0 112 136 16 32 32 0 32 0 0) ; ? (63) 45 + (0 112 136 152 168 176 128 112 0 0) ; @ (64) 46 + (0 32 80 136 136 248 136 136 0 0) ; A (65) 47 + (0 240 72 72 112 72 72 240 0 0) ; B (66) 48 + (0 112 136 128 128 128 136 112 0 0) ; C (67) 49 + (0 240 72 72 72 72 72 240 0 0) ; D (68) 50 + (0 248 128 128 240 128 128 248 0 0) ; E (69) 51 + (0 248 128 128 240 128 128 128 0 0) ; F (70) 52 + (0 112 136 128 128 152 136 112 0 0) ; G (71) 53 + (0 136 136 136 248 136 136 136 0 0) ; H (72) 54 + (0 112 32 32 32 32 32 112 0 0) ; I (73) 55 + (0 56 16 16 16 16 144 96 0 0) ; J (74) 56 + (0 136 144 160 192 160 144 136 0 0) ; K (75) 57 + (0 128 128 128 128 128 128 248 0 0) ; L (76) 58 + (0 136 136 216 168 136 136 136 0 0) ; M (77) 59 + (0 136 136 200 168 152 136 136 0 0) ; N (78) 60 + (0 112 136 136 136 136 136 112 0 0) ; O (79) 61 + (0 240 136 136 240 128 128 128 0 0) ; P (80) 62 + (0 112 136 136 136 136 168 112 8 0) ; Q (81) 63 + (0 240 136 136 240 160 144 136 0 0) ; R (82) 64 + (0 112 136 128 112 8 136 112 0 0) ; S (83) 65 + (0 248 32 32 32 32 32 32 0 0) ; T (84) 66 + (0 136 136 136 136 136 136 112 0 0) ; U (85) 67 + (0 136 136 136 80 80 80 32 0 0) ; V (86) 68 + (0 136 136 136 168 168 216 136 0 0) ; W (87) 69 + (0 136 136 80 32 80 136 136 0 0) ; X (88) 70 + (0 136 136 80 32 32 32 32 0 0) ; Y (89) 71 + (0 248 8 16 32 64 128 248 0 0) ; Z (90) 72 + (0 112 64 64 64 64 64 112 0 0) ; [ (91) 73 + (0 128 128 64 32 16 8 8 0 0) ; \ (92) 74 + (0 112 16 16 16 16 16 112 0 0) ; ] (93) 75 + (0 32 80 136 0 0 0 0 0 0) ; ^ (94) 76 + (0 0 0 0 0 0 0 0 248 0) ; _ (95) 77 + (32 16 0 0 0 0 0 0 0 0) ; ` (96) 78 + (0 0 0 112 8 120 136 120 0 0) ; a (97) 79 + (0 128 128 176 200 136 200 176 0 0) ; b (98) 80 + (0 0 0 112 136 128 136 112 0 0) ; c (99) 81 + (0 8 8 104 152 136 152 104 0 0) ; d (100) 82 + (0 0 0 112 136 248 128 112 0 0) ; e (101) 83 + (0 48 72 64 240 64 64 64 0 0) ; f (102) 84 + (0 0 0 120 136 136 120 8 136 112) ; g (103) 85 + (0 128 128 176 200 136 136 136 0 0) ; h (104) 86 + (0 32 0 96 32 32 32 112 0 0) ; i (105) 87 + (0 8 0 24 8 8 8 72 72 48) ; j (106) 88 + (0 128 128 136 144 224 144 136 0 0) ; k (107) 89 + (0 96 32 32 32 32 32 112 0 0) ; l (108) 90 + (0 0 0 208 168 168 168 136 0 0) ; m (109) 91 + (0 0 0 176 200 136 136 136 0 0) ; n (110) 92 + (0 0 0 112 136 136 136 112 0 0) ; o (111) 93 + (0 0 0 176 200 136 200 176 128 128) ; p (112) 94 + (0 0 0 104 152 136 152 104 8 8) ; q (113) 95 + (0 0 0 176 200 128 128 128 0 0) ; r (114) 96 + (0 0 0 112 128 112 8 240 0 0) ; s (115) 97 + (0 64 64 240 64 64 72 48 0 0) ; t (116) 98 + (0 0 0 136 136 136 152 104 0 0) ; u (117) 99 + (0 0 0 136 136 80 80 32 0 0) ; v (118) 100 + (0 0 0 136 136 168 168 80 0 0) ; w (119) 101 + (0 0 0 136 80 32 80 136 0 0) ; x (120) 102 + (0 0 0 136 136 152 104 8 136 112) ; y (121) 103 + (0 0 0 248 16 32 64 248 0 0) ; z (122) 104 + (0 24 32 16 96 16 32 24 0 0) ; { (123) 105 + (0 32 32 32 32 32 32 32 0 0) ; | (124) 106 + (0 96 16 32 24 32 16 96 0 0) ; } (125) 107 + (0 72 168 144 0 0 0 0 0 0) ; ~ (126) 108 + )))
+23 -20
fedac/native/cl/font.lisp
··· 1 - ;;; Font rendering — bitmap font draw/measure 1 + ;;; Font rendering — 6x10 bitmap font 2 2 3 3 (in-package :ac-native.font) 4 4 5 5 (defun font-init () 6 6 "Load font data. Called once at startup." 7 - ;; TODO: populate *font-8x8* from header data 7 + ;; Data is statically defined in font-data.lisp 8 8 ) 9 9 10 10 (defun font-draw (graph text x y &key (size 1)) 11 - "Draw TEXT at (X, Y) using the 8x8 bitmap font." 12 - (declare (ignore graph text x y size)) 13 - ;; TODO: implement 14 - ) 11 + "Draw TEXT at (X, Y) using the 6x10 bitmap font, scaled by SIZE." 12 + (declare (type fixnum x y size)) 13 + (let ((cx x)) 14 + (loop for ch across text do 15 + (let ((idx (- (char-code ch) 32))) 16 + (when (and (>= idx 0) (< idx 95)) 17 + (dotimes (row +font-h+) 18 + (let ((byte (aref *font-6x10* idx row))) 19 + (dotimes (col +font-w+) 20 + (when (logbitp (- 7 col) byte) 21 + (if (= size 1) 22 + (graph-plot graph (+ cx col) (+ y row)) 23 + ;; Scaled: draw size x size block per pixel 24 + (dotimes (dy size) 25 + (dotimes (dx size) 26 + (graph-plot graph 27 + (+ cx (* col size) dx) 28 + (+ y (* row size) dy))))))))))) 29 + (incf cx (* +font-w+ size))))) 15 30 16 31 (defun font-measure (text &key (size 1)) 17 - "Return the pixel width of TEXT in the 8x8 font." 18 - (declare (ignore size)) 19 - (* (length text) 8)) 20 - 21 - (defun font-draw-matrix (graph text x y size) 22 - "Draw TEXT using the MatrixChunky8 font." 23 - (declare (ignore graph text x y size)) 24 - ;; TODO: implement 25 - ) 26 - 27 - (defun font-measure-matrix (text size) 28 - "Return pixel width of TEXT in MatrixChunky8." 29 - (declare (ignore size)) 30 - (* (length text) 10)) 32 + "Return the pixel width of TEXT in the 6x10 font." 33 + (* (length text) +font-w+ size))
+42 -19
fedac/native/cl/framebuffer.lisp
··· 77 77 78 78 (defun fb-copy-scaled (src dst-ptr dst-w dst-h dst-stride scale) 79 79 "Copy SRC framebuffer to a foreign memory region at DST-PTR, scaling up by SCALE. 80 - Used by DRM present to blit the small render buffer to the display." 80 + Used by DRM present to blit the small render buffer to the display. 81 + Optimized: expand each source row into a temp row, then memcpy to fill scale rows." 81 82 (declare (optimize (speed 3) (safety 0)) 82 83 (type fixnum dst-w dst-h dst-stride scale) 83 84 (type framebuffer src)) 84 - (let ((sw (framebuffer-width src)) 85 - (sh (framebuffer-height src)) 86 - (sp (framebuffer-pixels src))) 87 - (dotimes (sy sh) 88 - (dotimes (sx sw) 89 - (let ((pixel (cffi:mem-aref sp :uint32 (+ sx (* sy sw))))) 90 - ;; Replicate pixel into scale x scale block 91 - (dotimes (dy scale) 92 - (let ((oy (* (+ (* (+ (* sy scale) dy) 1) 0) 1))) ; row offset placeholder 93 - (declare (ignore oy)) 94 - (let ((row (+ (* sy scale) dy))) 95 - (when (< row dst-h) 96 - (dotimes (dx scale) 97 - (let ((col (+ (* sx scale) dx))) 98 - (when (< col dst-w) 99 - (setf (cffi:mem-aref dst-ptr :uint32 100 - (+ col (* row dst-stride))) 101 - pixel))))))))))))) 85 + (let* ((sw (framebuffer-width src)) 86 + (sh (framebuffer-height src)) 87 + (sp (framebuffer-pixels src)) 88 + (row-bytes (* dst-stride 4))) ; bytes per destination row 89 + ;; For scale=1, use fast memcpy path 90 + (if (= scale 1) 91 + (let ((copy-w (min sw dst-w))) 92 + (dotimes (y (min sh dst-h)) 93 + (cffi:foreign-funcall "memcpy" 94 + :pointer (cffi:inc-pointer dst-ptr (* y row-bytes)) 95 + :pointer (cffi:inc-pointer sp (* y sw 4)) 96 + :unsigned-long (* copy-w 4) 97 + :pointer))) 98 + ;; General case: expand each source row, then replicate 99 + (let* ((expanded-w (min (* sw scale) dst-w)) 100 + (expanded-bytes (* expanded-w 4))) 101 + ;; Temp buffer for one expanded row 102 + (cffi:with-foreign-object (tmp :uint32 expanded-w) 103 + (dotimes (sy sh) 104 + (let ((dst-y0 (* sy scale))) 105 + (when (>= dst-y0 dst-h) (return)) 106 + ;; Expand source row: replicate each pixel `scale` times 107 + (let ((src-row-offset (* sy sw))) 108 + (dotimes (sx sw) 109 + (let ((pixel (cffi:mem-aref sp :uint32 (+ src-row-offset sx))) 110 + (dx0 (* sx scale))) 111 + (when (>= dx0 dst-w) (return)) 112 + (dotimes (dx scale) 113 + (let ((col (+ dx0 dx))) 114 + (when (>= col expanded-w) (return)) 115 + (setf (cffi:mem-aref tmp :uint32 col) pixel)))))) 116 + ;; Copy expanded row to each of the `scale` destination rows 117 + (dotimes (dy scale) 118 + (let ((row (+ dst-y0 dy))) 119 + (when (>= row dst-h) (return)) 120 + (cffi:foreign-funcall "memcpy" 121 + :pointer (cffi:inc-pointer dst-ptr (* row row-bytes)) 122 + :pointer tmp 123 + :unsigned-long expanded-bytes 124 + :pointer))))))))))
+56 -7
fedac/native/cl/input-keycodes.lisp
··· 1 - ;;; Linux input keycodes 1 + ;;; Linux input keycodes — full QWERTY for notepat 2 2 3 3 (in-package :ac-native.input) 4 4 5 + ;;; Event types 6 + (defconstant +ev-key+ 1) 7 + (defconstant +ev-rel+ 2) 8 + (defconstant +ev-abs+ 3) 9 + 10 + ;;; Special keys 5 11 (defconstant +key-esc+ 1) 6 - (defconstant +key-1+ 2) 7 12 (defconstant +key-backspace+ 14) 8 13 (defconstant +key-tab+ 15) 9 14 (defconstant +key-enter+ 28) 10 15 (defconstant +key-leftctrl+ 29) 11 - (defconstant +key-a+ 30) 12 16 (defconstant +key-space+ 57) 13 17 (defconstant +key-f1+ 59) 14 18 (defconstant +key-up+ 103) ··· 18 22 (defconstant +key-home+ 102) 19 23 (defconstant +key-end+ 107) 20 24 (defconstant +key-power+ 116) 25 + (defconstant +key-minus+ 12) 26 + (defconstant +key-equal+ 13) 21 27 22 - ;;; Event types 23 - (defconstant +ev-key+ 1) 24 - (defconstant +ev-rel+ 2) 25 - (defconstant +ev-abs+ 3) 28 + ;;; Number row (for octave selection) 29 + (defconstant +key-1+ 2) 30 + (defconstant +key-2+ 3) 31 + (defconstant +key-3+ 4) 32 + (defconstant +key-4+ 5) 33 + (defconstant +key-5+ 6) 34 + (defconstant +key-6+ 7) 35 + (defconstant +key-7+ 8) 36 + (defconstant +key-8+ 9) 37 + (defconstant +key-9+ 10) 38 + (defconstant +key-0+ 11) 39 + 40 + ;;; QWERTY top row 41 + (defconstant +key-q+ 16) 42 + (defconstant +key-w+ 17) 43 + (defconstant +key-e+ 18) 44 + (defconstant +key-r+ 19) 45 + (defconstant +key-t+ 20) 46 + (defconstant +key-y+ 21) 47 + (defconstant +key-u+ 22) 48 + (defconstant +key-i+ 23) 49 + (defconstant +key-o+ 24) 50 + (defconstant +key-p+ 25) 51 + (defconstant +key-leftbrace+ 26) 52 + (defconstant +key-rightbrace+ 27) 53 + 54 + ;;; QWERTY home row 55 + (defconstant +key-a+ 30) 56 + (defconstant +key-s+ 31) 57 + (defconstant +key-d+ 32) 58 + (defconstant +key-f+ 33) 59 + (defconstant +key-g+ 34) 60 + (defconstant +key-h+ 35) 61 + (defconstant +key-j+ 36) 62 + (defconstant +key-k+ 37) 63 + (defconstant +key-l+ 38) 64 + (defconstant +key-semicolon+ 39) 65 + (defconstant +key-apostrophe+ 40) 66 + 67 + ;;; QWERTY bottom row 68 + (defconstant +key-z+ 44) 69 + (defconstant +key-x+ 45) 70 + (defconstant +key-c+ 46) 71 + (defconstant +key-v+ 47) 72 + (defconstant +key-b+ 48) 73 + (defconstant +key-n+ 49) 74 + (defconstant +key-m+ 50) 26 75 27 76 ;;; struct input_event (24 bytes on x86-64) 28 77 (cffi:defcstruct input-event
+344 -40
fedac/native/cl/main.lisp
··· 1 - ;;; Main entry point — AC Native OS (Common Lisp edition) 2 - ;;; First boot: just show a colored screen to prove DRM works 1 + ;;; Notepat — AC Native OS musical keyboard instrument (Common Lisp) 2 + ;;; Port of fedac/native/pieces/notepat.mjs core functionality 3 3 4 4 (in-package :ac-native) 5 5 6 6 (defvar *running* t "Main loop flag.") 7 + 8 + ;;; ── Pixel scale ── 7 9 8 10 (defun compute-pixel-scale (display-w) 9 - "Compute pixel scale targeting ~300px wide." 10 - (let ((target (max 1 (min 16 (floor display-w 300))))) 11 + "Compute pixel scale targeting ~200px wide (bigger pixels)." 12 + (let ((target (max 1 (min 16 (floor display-w 200))))) 11 13 (loop for delta from 0 to 3 do 12 14 (let ((s (+ target delta))) 13 15 (when (and (>= s 1) (<= s 16) (zerop (mod display-w s))) ··· 17 19 (return-from compute-pixel-scale s)))) 18 20 target)) 19 21 22 + ;;; ── Music theory ── 23 + 24 + (defvar *chromatic* #("c" "c#" "d" "d#" "e" "f" "f#" "g" "g#" "a" "a#" "b")) 25 + 26 + (defun note-to-freq (note-name octave) 27 + "Convert note name and octave to frequency in Hz. 28 + A4 = 440Hz. Uses equal temperament." 29 + (let ((idx (position note-name *chromatic* :test #'string=))) 30 + (if idx 31 + (* 440.0d0 (expt 2.0d0 (+ (- octave 4) (/ (- idx 9) 12.0d0)))) 32 + 440.0d0))) 33 + 34 + ;;; ── Note colors (chromatic rainbow) ── 35 + 36 + (defvar *note-colors* 37 + '(("c" 255 30 30) ; red 38 + ("c#" 255 80 0) ; red-orange 39 + ("d" 255 150 0) ; orange 40 + ("d#" 200 200 0) ; yellow-green 41 + ("e" 230 220 0) ; yellow 42 + ("f" 30 200 30) ; green 43 + ("f#" 0 200 180) ; teal 44 + ("g" 30 100 255) ; blue 45 + ("g#" 80 50 255) ; indigo 46 + ("a" 140 30 220) ; purple 47 + ("a#" 200 30 150) ; magenta 48 + ("b" 200 50 255))) ; violet 49 + 50 + (defun note-color-rgb (note-name) 51 + "Return (r g b) list for a note name." 52 + (let ((entry (assoc note-name *note-colors* :test #'string=))) 53 + (if entry (cdr entry) '(80 80 80)))) 54 + 55 + ;;; ── Keyboard mapping: evdev keycode → (note-name . octave-offset) ── 56 + 57 + (defvar *key-note-map* (make-hash-table :test 'eql)) 58 + 59 + (defun init-key-note-map () 60 + "Populate keycode → note mapping (QWERTY layout matching JS notepat)." 61 + (clrhash *key-note-map*) 62 + ;; Lower octave naturals 63 + (setf (gethash ac-native.input:+key-c+ *key-note-map*) '("c" . 0)) 64 + (setf (gethash ac-native.input:+key-d+ *key-note-map*) '("d" . 0)) 65 + (setf (gethash ac-native.input:+key-e+ *key-note-map*) '("e" . 0)) 66 + (setf (gethash ac-native.input:+key-f+ *key-note-map*) '("f" . 0)) 67 + (setf (gethash ac-native.input:+key-g+ *key-note-map*) '("g" . 0)) 68 + (setf (gethash ac-native.input:+key-a+ *key-note-map*) '("a" . 0)) 69 + (setf (gethash ac-native.input:+key-b+ *key-note-map*) '("b" . 0)) 70 + ;; Lower octave sharps 71 + (setf (gethash ac-native.input:+key-v+ *key-note-map*) '("c#" . 0)) 72 + (setf (gethash ac-native.input:+key-s+ *key-note-map*) '("d#" . 0)) 73 + (setf (gethash ac-native.input:+key-w+ *key-note-map*) '("f#" . 0)) 74 + (setf (gethash ac-native.input:+key-r+ *key-note-map*) '("g#" . 0)) 75 + (setf (gethash ac-native.input:+key-q+ *key-note-map*) '("a#" . 0)) 76 + ;; Upper octave naturals 77 + (setf (gethash ac-native.input:+key-h+ *key-note-map*) '("c" . 1)) 78 + (setf (gethash ac-native.input:+key-i+ *key-note-map*) '("d" . 1)) 79 + (setf (gethash ac-native.input:+key-j+ *key-note-map*) '("e" . 1)) 80 + (setf (gethash ac-native.input:+key-k+ *key-note-map*) '("f" . 1)) 81 + (setf (gethash ac-native.input:+key-l+ *key-note-map*) '("g" . 1)) 82 + (setf (gethash ac-native.input:+key-m+ *key-note-map*) '("a" . 1)) 83 + (setf (gethash ac-native.input:+key-n+ *key-note-map*) '("b" . 1)) 84 + ;; Upper octave sharps 85 + (setf (gethash ac-native.input:+key-t+ *key-note-map*) '("c#" . 1)) 86 + (setf (gethash ac-native.input:+key-y+ *key-note-map*) '("d#" . 1)) 87 + (setf (gethash ac-native.input:+key-u+ *key-note-map*) '("f#" . 1)) 88 + (setf (gethash ac-native.input:+key-o+ *key-note-map*) '("g#" . 1)) 89 + (setf (gethash ac-native.input:+key-p+ *key-note-map*) '("a#" . 1)) 90 + ;; Extension: +2 octave 91 + (setf (gethash ac-native.input:+key-semicolon+ *key-note-map*) '("c" . 2)) 92 + (setf (gethash ac-native.input:+key-apostrophe+ *key-note-map*) '("c#" . 2)) 93 + (setf (gethash ac-native.input:+key-rightbrace+ *key-note-map*) '("d" . 2)) 94 + ;; Sub-octave 95 + (setf (gethash ac-native.input:+key-z+ *key-note-map*) '("a#" . -1)) 96 + (setf (gethash ac-native.input:+key-x+ *key-note-map*) '("b" . -1))) 97 + 98 + ;;; ── Wave types ── 99 + 100 + (defvar *wave-names* #("sine" "triangle" "sawtooth" "square" "noise")) 101 + (defvar *wave-index* 0) 102 + (defvar *octave* 4) 103 + 104 + ;;; ── Active voices and trails ── 105 + 106 + (defvar *active-voices* (make-hash-table :test 'eql) 107 + "Keycode → voice-id for currently held keys.") 108 + 109 + (defvar *active-notes* (make-hash-table :test 'eql) 110 + "Keycode → (note-name . actual-octave) for currently held keys.") 111 + 112 + (defvar *trails* (make-hash-table :test 'equal) 113 + "note-name → brightness (1.0 → 0.0) for recently released notes.") 114 + 115 + ;;; ── Background color state ── 116 + 117 + (defvar *bg-r* 20) 118 + (defvar *bg-g* 20) 119 + (defvar *bg-b* 25) 120 + 121 + ;;; ── FPS tracking ── 122 + 123 + (defvar *fps-display* 0) 124 + (defvar *fps-accum* 0.0d0) 125 + (defvar *fps-samples* 0) 126 + (defvar *fps-last-time* 0.0d0) 127 + 128 + ;;; ── ESC triple-press ── 129 + 130 + (defvar *esc-count* 0) 131 + (defvar *esc-last-frame* 0) 132 + 133 + ;;; ── Main ── 134 + 20 135 (defun main () 21 - "AC Native OS entry point." 22 - ;; Log to stderr (visible on serial console) 136 + "AC Native OS entry point — boots directly into notepat." 23 137 (format *error-output* "~%════════════════════════════════════~%") 24 - (format *error-output* " AC Native OS (Common Lisp)~%") 138 + (format *error-output* " notepat (Common Lisp)~%") 25 139 (format *error-output* " SBCL ~A~%" (lisp-implementation-version)) 26 140 (format *error-output* "════════════════════════════════════~%~%") 27 141 (force-output *error-output*) 28 142 29 - ;; Try to init display 143 + ;; Init key map 144 + (init-key-note-map) 145 + 146 + ;; Init display 30 147 (let ((display (handler-case (ac-native.drm:drm-init) 31 148 (error (e) 32 - (format *error-output* "[cl] DRM init error: ~A~%" e) 149 + (format *error-output* "[notepat] DRM error: ~A~%" e) 33 150 (force-output *error-output*) 34 151 nil)))) 35 152 (unless display 36 - (format *error-output* "[cl] FATAL: no display — sleeping 30s~%") 153 + (format *error-output* "[notepat] FATAL: no display~%") 37 154 (force-output *error-output*) 38 155 (sleep 30) 39 156 (return-from main 1)) ··· 44 161 (sw (floor dw scale)) 45 162 (sh (floor dh scale)) 46 163 (screen (fb-create sw sh)) 47 - (graph (make-graph :fb screen :screen screen))) 164 + (graph (make-graph :fb screen :screen screen)) 165 + (input (ac-native.input:input-init dw dh scale)) 166 + (audio (ac-native.audio:audio-init)) 167 + (frame 0)) 48 168 49 - (format *error-output* "[cl] display: ~Dx~D scale: ~D screen: ~Dx~D~%" 169 + (format *error-output* "[notepat] ~Dx~D scale:~D → ~Dx~D~%" 50 170 dw dh scale sw sh) 171 + (format *error-output* "[notepat] audio: ~A~%" 172 + (if audio "OK" "FAILED")) 51 173 (force-output *error-output*) 52 174 53 - ;; Main loop — just cycle colors 175 + ;; Font init 176 + (font-init) 177 + 178 + ;; Main loop 54 179 (setf *running* t) 55 - (let ((frame 0)) 56 - (unwind-protect 57 - (loop while *running* do 58 - (incf frame) 59 - ;; Cycle background 60 - (let* ((t-val (* frame 0.02)) 61 - (r (floor (+ 40 (* 40 (sin t-val))))) 62 - (g (floor (+ 20 (* 20 (sin (* t-val 1.3)))))) 63 - (b (floor (+ 80 (* 80 (sin (* t-val 0.7))))))) 64 - (graph-wipe graph (make-color :r r :g g :b b)) 180 + (unwind-protect 181 + (loop while *running* do 182 + (incf frame) 65 183 66 - ;; White box in center 67 - (graph-ink graph (make-color :r 255 :g 255 :b 255 :a 200)) 68 - (graph-box graph (- (floor sw 2) 30) (- (floor sh 2) 30) 60 60) 184 + ;; FPS tracking 185 + (let ((now (monotonic-time-ms))) 186 + (when (> *fps-last-time* 0.0d0) 187 + (incf *fps-accum* (- now *fps-last-time*)) 188 + (incf *fps-samples*) 189 + (when (>= *fps-samples* 30) 190 + (setf *fps-display* (round (/ 30000.0d0 *fps-accum*))) 191 + (setf *fps-accum* 0.0d0 *fps-samples* 0))) 192 + (setf *fps-last-time* now)) 69 193 70 - ;; Orange circle 71 - (graph-ink graph (make-color :r 255 :g 140 :b 50)) 72 - (graph-circle graph (floor sw 2) (floor sh 2) 20)) 194 + ;; ── Input ── 195 + (dolist (ev (ac-native.input:input-poll input)) 196 + (let ((type (ac-native.input:event-type ev)) 197 + (code (ac-native.input:event-code ev))) 73 198 74 - ;; Present 75 - (ac-native.drm:drm-present display screen scale) 76 - (frame-sync-60fps) 199 + ;; ── Key down ── 200 + (when (eq type :key-down) 201 + ;; ESC: triple-press to quit 202 + (when (= code ac-native.input:+key-esc+) 203 + (if (> (- frame *esc-last-frame*) 90) 204 + (setf *esc-count* 0)) 205 + (incf *esc-count*) 206 + (setf *esc-last-frame* frame) 207 + (when (and audio (< *esc-count* 3)) 208 + (audio-synth audio :type 3 ; square 209 + :tone (if (= *esc-count* 1) 440.0d0 660.0d0) 210 + :duration 0.08d0 :volume 0.15d0 211 + :attack 0.002d0 :decay 0.06d0)) 212 + (when (>= *esc-count* 3) 213 + (setf *running* nil))) 77 214 78 - ;; Exit after 10 seconds (for testing) 79 - (when (> frame 600) 80 - (setf *running* nil))) 215 + ;; Power button 216 + (when (= code ac-native.input:+key-power+) 217 + (setf *running* nil)) 81 218 82 - ;; Cleanup 83 - (fb-destroy screen) 84 - (ac-native.drm:drm-destroy display) 85 - (format *error-output* "[cl] shutdown~%") 86 - (force-output *error-output*)))))) 219 + ;; Number keys: set octave 220 + (when (and (>= code ac-native.input:+key-1+) 221 + (<= code ac-native.input:+key-9+)) 222 + (setf *octave* (1+ (- code ac-native.input:+key-1+)))) 223 + 224 + ;; Arrow up/down: octave 225 + (when (= code ac-native.input:+key-up+) 226 + (setf *octave* (min 9 (1+ *octave*)))) 227 + (when (= code ac-native.input:+key-down+) 228 + (setf *octave* (max 1 (1- *octave*)))) 229 + 230 + ;; Tab: cycle wave type 231 + (when (= code ac-native.input:+key-tab+) 232 + (setf *wave-index* (mod (1+ *wave-index*) 5)) 233 + ;; Confirmation blip 234 + (when audio 235 + (let ((tones #(660.0d0 550.0d0 440.0d0 330.0d0 220.0d0))) 236 + (audio-synth audio :type *wave-index* 237 + :tone (aref tones *wave-index*) 238 + :duration 0.07d0 :volume 0.18d0 239 + :attack 0.002d0 :decay 0.06d0)))) 240 + 241 + ;; Note keys 242 + (let ((mapping (gethash code *key-note-map*))) 243 + (when (and mapping 244 + (not (gethash code *active-voices*)) 245 + audio) 246 + (let* ((note-name (car mapping)) 247 + (oct-delta (cdr mapping)) 248 + (actual-octave (+ *octave* oct-delta)) 249 + (freq (note-to-freq note-name actual-octave)) 250 + ;; Pan: lower notes left, higher notes right 251 + (idx (position note-name *chromatic* :test #'string=)) 252 + (semitones (+ (* (- actual-octave 4) 12) (or idx 0))) 253 + (pan (max -0.8d0 (min 0.8d0 (/ (- semitones 12) 15.0d0)))) 254 + (voice-id (audio-synth audio 255 + :type *wave-index* 256 + :tone freq 257 + :volume 0.7d0 258 + :duration 0 ; sustain 259 + :attack 0.005d0 260 + :decay 0.1d0 261 + :pan pan))) 262 + (setf (gethash code *active-voices*) voice-id) 263 + (setf (gethash code *active-notes*) 264 + (cons note-name actual-octave)))))) 265 + 266 + ;; ── Key up ── 267 + (when (eq type :key-up) 268 + (let ((voice-id (gethash code *active-voices*))) 269 + (when (and voice-id audio) 270 + (audio-synth-kill audio voice-id) 271 + (remhash code *active-voices*) 272 + ;; Start trail 273 + (let ((note-info (gethash code *active-notes*))) 274 + (when note-info 275 + (setf (gethash note-info *trails*) 1.0) 276 + (remhash code *active-notes*)))))))) 277 + 278 + ;; ── Trail decay ── 279 + (let ((dead nil)) 280 + (maphash (lambda (note val) 281 + (let ((new-val (- val 0.025))) 282 + (if (<= new-val 0.0) 283 + (push note dead) 284 + (setf (gethash note *trails*) new-val)))) 285 + *trails*) 286 + (dolist (n dead) (remhash n *trails*))) 287 + 288 + ;; ── Compute background color from active notes ── 289 + (let ((n (hash-table-count *active-notes*))) 290 + (if (> n 0) 291 + (let ((tr 0) (tg 0) (tb 0)) 292 + (maphash (lambda (code note-info) 293 + (declare (ignore code)) 294 + (let ((rgb (note-color-rgb (car note-info)))) 295 + (incf tr (first rgb)) 296 + (incf tg (second rgb)) 297 + (incf tb (third rgb)))) 298 + *active-notes*) 299 + ;; Lerp toward target (darkened) 300 + (let ((target-r (floor (* (floor tr n) 35) 100)) 301 + (target-g (floor (* (floor tg n) 35) 100)) 302 + (target-b (floor (* (floor tb n) 35) 100))) 303 + (setf *bg-r* (+ *bg-r* (floor (- target-r *bg-r*) 4))) 304 + (setf *bg-g* (+ *bg-g* (floor (- target-g *bg-g*) 4))) 305 + (setf *bg-b* (+ *bg-b* (floor (- target-b *bg-b*) 4))))) 306 + ;; Decay to dark 307 + (progn 308 + (setf *bg-r* (+ *bg-r* (floor (- 20 *bg-r*) 8))) 309 + (setf *bg-g* (+ *bg-g* (floor (- 20 *bg-g*) 8))) 310 + (setf *bg-b* (+ *bg-b* (floor (- 25 *bg-b*) 8)))))) 311 + 312 + ;; ── Paint ── 313 + (graph-wipe graph (make-color :r *bg-r* :g *bg-g* :b *bg-b*)) 314 + 315 + ;; Draw trails — horizontal bars per note+octave 316 + (maphash (lambda (trail-key val) 317 + ;; trail-key is (note-name . octave) 318 + (let* ((note-name (car trail-key)) 319 + (oct (cdr trail-key)) 320 + (rgb (note-color-rgb note-name)) 321 + (note-idx (or (position note-name *chromatic* :test #'string=) 0)) 322 + ;; Unique Y per note+octave: semitone index relative to octave 1 323 + (semi (+ (* (- oct 1) 12) note-idx)) 324 + (total-semitones (* 9 12)) ; octaves 1-9 325 + (bar-h (max 2 (floor (- sh 30) total-semitones))) 326 + (bar-y (+ 14 (floor (* semi (- sh 30)) total-semitones))) 327 + (bar-w (max 1 (floor (* val sw)))) 328 + (bar-x (floor (- sw bar-w) 2)) 329 + (alpha (max 1 (min 255 (floor (* val 200)))))) 330 + (graph-ink graph (make-color :r (first rgb) 331 + :g (second rgb) 332 + :b (third rgb) 333 + :a alpha)) 334 + (graph-box graph bar-x bar-y bar-w bar-h))) 335 + *trails*) 336 + 337 + ;; Draw active note indicators — bright bars 338 + (maphash (lambda (code note-info) 339 + (declare (ignore code)) 340 + (let* ((note-name (car note-info)) 341 + (oct (cdr note-info)) 342 + (rgb (note-color-rgb note-name)) 343 + (note-idx (or (position note-name *chromatic* :test #'string=) 0)) 344 + (semi (+ (* (- oct 1) 12) note-idx)) 345 + (total-semitones (* 9 12)) 346 + (bar-h (max 2 (floor (- sh 30) total-semitones))) 347 + (bar-y (+ 14 (floor (* semi (- sh 30)) total-semitones)))) 348 + (graph-ink graph (make-color :r (min 255 (+ (first rgb) 40)) 349 + :g (min 255 (+ (second rgb) 40)) 350 + :b (min 255 (+ (third rgb) 40)) 351 + :a 220)) 352 + (graph-box graph 0 bar-y sw bar-h))) 353 + *active-notes*) 354 + 355 + ;; Status bar (bottom) 356 + (let ((status (format nil "~A OCT:~D ~Dfps" 357 + (aref *wave-names* *wave-index*) 358 + *octave* 359 + *fps-display*))) 360 + (graph-ink graph (make-color :r 180 :g 180 :b 180 :a 200)) 361 + (font-draw graph status 3 (- sh 12))) 362 + 363 + ;; "notepat" title (top-left, dim) 364 + (graph-ink graph (make-color :r 100 :g 100 :b 110 :a 150)) 365 + (font-draw graph "notepat" 3 3) 366 + 367 + ;; Active voice count (top-right) 368 + (let ((vc (hash-table-count *active-voices*))) 369 + (when (> vc 0) 370 + (let ((txt (format nil "~D" vc))) 371 + (graph-ink graph (make-color :r 200 :g 200 :b 200 :a 180)) 372 + (font-draw graph txt (- sw (* (length txt) 6) 3) 3)))) 373 + 374 + ;; ── Present ── 375 + (ac-native.drm:drm-present display screen scale) 376 + (frame-sync-60fps)) 377 + 378 + ;; ── Cleanup ── 379 + ;; Kill all active voices 380 + (when audio 381 + (maphash (lambda (code voice-id) 382 + (declare (ignore code)) 383 + (audio-synth-kill audio voice-id)) 384 + *active-voices*) 385 + (audio-destroy audio)) 386 + (ac-native.input:input-destroy input) 387 + (fb-destroy screen) 388 + (ac-native.drm:drm-destroy display) 389 + (format *error-output* "[notepat] shutdown~%") 390 + (force-output *error-output*)))))
+21 -4
fedac/native/cl/packages.lisp
··· 38 38 #:graph-plot #:graph-line #:graph-box #:graph-circle)) 39 39 40 40 (defpackage :ac-native.font 41 - (:use :cl :ac-native.graph :ac-native.framebuffer) 42 - (:export #:font-init #:font-draw #:font-measure 43 - #:font-draw-matrix #:font-measure-matrix)) 41 + (:use :cl :ac-native.color :ac-native.graph :ac-native.framebuffer) 42 + (:export #:font-init #:font-draw #:font-measure)) 44 43 45 44 (defpackage :ac-native.input 46 45 (:use :cl :cffi :ac-native.syscalls) 47 46 (:export #:input-init #:input-destroy #:input-poll 48 - #:make-event #:event-type #:event-key #:event-x #:event-y)) 47 + #:make-event #:event-type #:event-key #:event-code #:event-x #:event-y 48 + ;; Special keys 49 + #:+key-esc+ #:+key-backspace+ #:+key-tab+ #:+key-enter+ 50 + #:+key-leftctrl+ #:+key-space+ #:+key-f1+ 51 + #:+key-up+ #:+key-down+ #:+key-left+ #:+key-right+ 52 + #:+key-home+ #:+key-end+ #:+key-power+ 53 + #:+key-minus+ #:+key-equal+ 54 + ;; Number row 55 + #:+key-1+ #:+key-2+ #:+key-3+ #:+key-4+ #:+key-5+ 56 + #:+key-6+ #:+key-7+ #:+key-8+ #:+key-9+ #:+key-0+ 57 + ;; QWERTY rows 58 + #:+key-q+ #:+key-w+ #:+key-e+ #:+key-r+ #:+key-t+ 59 + #:+key-y+ #:+key-u+ #:+key-i+ #:+key-o+ #:+key-p+ 60 + #:+key-leftbrace+ #:+key-rightbrace+ 61 + #:+key-a+ #:+key-s+ #:+key-d+ #:+key-f+ #:+key-g+ 62 + #:+key-h+ #:+key-j+ #:+key-k+ #:+key-l+ 63 + #:+key-semicolon+ #:+key-apostrophe+ 64 + #:+key-z+ #:+key-x+ #:+key-c+ #:+key-v+ #:+key-b+ 65 + #:+key-n+ #:+key-m+)) 49 66 50 67 (defpackage :ac-native.alsa 51 68 (:use :cl :cffi)