(rnrs io ports)
(srfi srfi-1)
(srfi srfi-4)
+ (srfi srfi-26)
(ice-9 format)
;;; stty is less of a pain, just be evil and system
(system "stty -F /dev/ttyACM0 cs8 115200 ignbrk -brkint -icrnl -imaxbel -opost -onlcr -isig -icanon -iexten -echo -echoe -echok -echoctl -echoke noflsh -ixon -crtscts")
-(sleep 3) ; wait for arduino to finish booting
+(set-current-error-port (open-file "/tmp/led.log" "w0"))
+(sleep 1) ; wait for arduino to finish booting
(set! *random-state* (random-state-from-platform))
(define serial-lock (make-mutex))
(define pi 3.141592653589793)
+(define tau (* 2 pi))
(define (deg->rad degrees)
(* degrees (/ pi 180)))
(values (+ r₁ m) (+ g₁ m) (+ b₁ m)))))
(define (set-led-color/primitive! r g b)
- ;; if put-bytevector is not atomic, may need to have a writer thread
- (with-mutex serial-lock
- (put-bytevector serial-out (u8vector r g b))
- (read-line serial-in 'concat)))
+ (call-with-blocked-asyncs (lambda ()
+ (with-mutex serial-lock
+ (format serial-out "~A,~A,~A~c" r g b #\return)
+ (read-line serial-in 'concat)))))
(define (set-led-hsv! h s v)
(with-mutex color-lock
(set! current-color (vector h s v))
(receive (r g b)
(hsv->rgb h s v)
- (let ((r' (inexact->exact (truncate (* (* r 1) 255))))
- (g' (inexact->exact (truncate (* (* g 1) 255))))
- (b' (inexact->exact (truncate (* (* b 1) 255)))))
+ (let ((r' (inexact->exact (truncate (* (* r 1) pwm-resolution))))
+ (g' (inexact->exact (truncate (* (* g 1) pwm-resolution))))
+ (b' (inexact->exact (truncate (* (* b 1) pwm-resolution)))))
(set-led-color/primitive! r' g' b')))))
(define zzz (if (and (> (length (command-line)) 1)
(number? (string->number (second (command-line)))))
(string->number (second (command-line)))
- 45000))
+ 4500))
(display (command-line))
-(let ((zone-out (format #f "hey man i'm getting zzzzzzzzzzooooned out ~A~%" zzz)))
+#;(let ((zone-out (format #f "hey man i'm getting zzzzzzzzzzooooned out ~A~%" zzz)))
(display zone-out)
(system (format #f "echo \"~A\" | festival --tts" zone-out)))
-(define tick-increment (/ (* 2 pi) (* 360 10)))
-
-(define (random-ticks n) (* tick-increment (random n)))
-
-(define (hsv-fade)
- (let loop ()
- (with-mutex color-lock
- (match current-color
- (#(h s v)
- (let ((h (euclidean-remainder (+ h tick-increment) (* 2 pi))))
- (display (set-led-hsv! h s v) (current-error-port))))))
- (usleep zzz)
- (loop))
-
-#;
- (let loop ((tick 0))
- (let ((h (euclidean-remainder tick (* 2 pi))))
- (display (set-led-hsv! h 1.0 1.0) (current-error-port))
- (usleep zzz)
- (loop (+ tick-increment tick)))))
-
+(define (rgb-test)
+ (letrec ((loop (lambda (i fn)
+ (cond ((= i pwm-resolution) #t)
+ (else (display (fn i))
+ (usleep zzz)
+ (loop (1+ i) fn))))))
+ (while #t
+ (loop 0 (cut set-led-color/primitive! <> 0 0))
+ (loop 0 (cut set-led-color/primitive! 0 <> 0))
+ (loop 0 (cut set-led-color/primitive! 0 0 <>)))))
;;; Multi-processing is for hep cats
(kill-fx))
(set! running-fx (make-thread fx)))
-(define (reverse-tick!)
- (set! tick-increment (- tick-increment)))
-
-
(define (led-command-handler request body)
(match (split-and-decode-uri-path (uri-path (request-uri request)))
(("set" "rgb" r g b)
(values '((content-type . (text/plain)))
"luser\n"))))
-(set-current-error-port (open-file "/tmp/led.log" "w0"))
+
(define (start-led-server)
(set! server-thread (make-thread (lambda () (run-server led-command-handler 'http '(#:port 58080))))))
+
+(define (current-time/usec)
+ (let ((now (gettimeofday)))
+ (+ (* (car now) (expt 10 6)) (cdr now))))
+
+(define (fx-driver fx)
+ (let loop ((t (current-time/usec))
+ (dt 0))
+ ;; We live a moment in the past
+ (receive (h s v)
+ (fx t dt)
+ (display (set-led-hsv! h s v) (current-error-port)))
+ (let ((now (current-time/usec)))
+ (loop now (- now t)))))
+
+(define (hsv-fx t dt)
+ (define tick-increment (/ (deg->rad 1280) (expt 10 6)))
+ (with-mutex color-lock
+ (match current-color
+ (#(h s v)
+ (let ((h (euclidean-remainder (+ h (* tick-increment dt)) tau)))
+ (values h s v))))))
+
+
--- /dev/null
+;;; Simple program to parse catver.ini
+;;; (http://www.progettosnaps.net/catver/) and filter the results.
+;;; 2019 Clinton Ebadi <clinton@unknownlamer.org>
+;;; CC0 To the extent possible under law, Clinton Ebadi has waived all
+;;; copyright and related or neighboring rights to MAME Category
+;;; Filter.
+
+(quicklisp:quickload '(:parser.ini :cl-ppcre :alexandria :arnesi))
+
+(import '(alexandria:read-file-into-string))
+
+;; fixme: parser.ini doesn't handle DOS line endings so file has to be
+;; converted to unix line endings. manually open the stream instead
+;; after I remember how external formats work again.
+
+;; fixme: since I'm using an ad-hoc parser and put zero effort into this, you have to edit catver.ini and extract just the [Category] section
+(defparameter *catver-source* (pathname "/home/clinton/src/repos/local/scratch/mame-catver-filter/pS_CatVer_230/catver-only.ini"))
+
+;; parser.ini barfed on catver (ate all ram and then died...)
+;; (defparameter *parsed-catver*
+;; (let ((parser.ini:*value-terminating-whitespace-expression* #\Newline))
+;; (parser.ini:parse *catver-source* 'list)))
+
+(defparameter *catver-hash* (make-hash-table :test 'equal))
+
+(with-open-file (catver-stream *catver-source* )
+ (arnesi:awhile (read-line catver-stream nil nil)
+ (cl-ppcre:register-groups-bind (game category)
+ ("([^=]+)=([^=]+)" arnesi:it)
+ (format t "KEY ~A VALUE ~A~%" game category)
+ (setf (gethash category *catver-hash*) (cons game (gethash category *catver-hash*))))))
+
+(defun print-cats ()
+ (format t "(")
+ (maphash (lambda (key value)
+ (format t "~S~%" key))
+ *catver-hash*)
+ (format t ")"))
+
+(defun filter-games (categories)
+ (mapcan (lambda (cat) (gethash cat *catver-hash*)) categories))
+
+(defun dump-games (filename categories)
+ (with-open-file (outf filename :direction :output :if-exists :error)
+ (mapc (lambda (x) (format outf "~A~%" x)) (filter-games categories))))
+
+
+;; 2020-11-19: might want to review these for new categories, haven't checked since mame 0.214
+(defparameter *filtered-cats*
+ '("Maze / Shooter Small"
+ "Sports / Football"
+ "Sports / Soccer"
+ "Driving / Truck Guide"
+ "Shooter / Flying Vertical"
+ "Sports / Basketball"
+ "Sports / Baseball"
+ "Driving / 1st Person"
+ "Shooter / Gun"
+ "Sports / Wrestling"
+ "Shooter / Field"
+ "Sports / Tennis"
+ "Shooter / Flying Horizontal"
+ "Puzzle / Match"
+ "Casino / Misc."
+ "Driving / Motorbike"
+ "Maze / Collect"
+ "Fighter / 2.5D"
+ "Sports / Skateboarding"
+ "Shooter / Misc. Horizontal"
+ "Sports / Track & Field"
+ "Sports / Pool"
+ "Computer / Training Board"
+ "Shooter / Gallery"
+ "Shooter / Flying (chase view)"
+ "Platform / Run Jump"
+ "Sports / Volleyball"
+ "Driving / Race 1st Person"
+ "Fighter / Versus"
+ "Platform / Shooter Scrolling"
+ "Maze / Change Surface"
+ "Driving / Race (chase view)"
+ "Shooter / Misc. Vertical"
+ "Puzzle / Sliding"
+ "Shooter / Flying 1st Person"
+ "Driving / Plane"
+ "Sports / Fishing"
+ "Platform / Fighter Scrolling"
+ "Sports / Golf"
+ "Platform / Run, Jump & Scrolling"
+ "Shooter / 1st Person"
+ "Maze / Shooter Large"
+ "Shooter / 3rd Person"
+ "Sports / Bowling"
+ "Shooter / Misc."
+ "Sports / Skiing"
+ "Shooter / Walking"
+ "Sports / Darts"
+ "Sports / Hockey"
+ "Maze / Outline"
+ "Driving / Race Track"
+ "Climbing / Tree - Plant"
+ "Driving / Misc."
+ "Shooter / Flying"
+ "Shooter / Driving (chase view)"
+ "Driving / Boat"
+ "Puzzle / Drop"
+ "Puzzle / Misc."
+ "Sports / Misc."
+ "Platform / Fighter"
+ "Fighter / 2D"
+ "Driving / Race (chase view) Bike"
+ "Ball & Paddle / Breakout"
+ "Sports / Horse Racing"
+ "Sports / Armwrestling"
+ "Sports / Boxing"
+ "Fighter / 3D"
+ "Shooter / Driving"
+ "Shooter / Versus"
+ "Shooter / Driving Vertical"
+ "Fighter / Vertical"
+ "Driving / Race"
+ "Rhythm / Dance"
+ "Maze / Surround"
+ "Misc. / Toy Cars"
+ "Puzzle / Maze"
+ "Platform / Shooter"
+ "Maze / Escape"
+ "Shooter / Driving Diagonal"
+ "Ball & Paddle / Pong"
+ "Maze / Collect & Put"
+ "Rhythm / Instruments"
+ "Fighter / Misc."
+ "Maze / Digging"
+ "Maze / Blocks"
+ "Maze / Cross"
+ "Shooter / Driving Horizontal"
+ "Fighter / Field"
+ "Sports / Bull Fighting"
+ "Shooter / Driving 1st Person"
+ "Maze / Ladders"
+ "Puzzle / Reconstruction"
+ "Maze / Driving"
+ "Maze / Defeat Enemies"
+ "Ball & Paddle / Jump and Touch"
+ "Climbing / Building"
+ "Puzzle / Outline"
+ "Driving / Race Bike"
+ "Shooter / Command"
+ "Sports / Sumo"
+ "Fighter / Compilation"
+ "Rhythm / Misc."
+ "Driving / Demolition Derby"
+ "Maze / Paint"
+ "Sports / Rugby Football"
+ "Fighter / Driving Vertical"
+ "Fighter / Versus Co-op"
+ "Maze / Fighter"
+ "Misc. / Prediction"
+ "Fighter / Asian 3D"
+ "Maze / Move and Sort"
+ "Sports / Hang Gliding"
+ "Maze / Integrate"
+ "Shooter / Flying Diagonal"
+ "Platform / Maze"
+ "Sports / Horseshoes"
+ "Sports / Handball"
+ "Maze / Ball Guide"
+ "Misc. / Reflex"
+ "Driving / Motorbike (Motocross)"
+ "Driving / Guide and Collect"
+ "Sports / Volley - Soccer"
+ "Misc. / Versus"
+ "Tabletop / Othello - Reversi"
+ "Driving / Landing"
+ "Music / Drum Machine"
+ "Maze / Marble Madness"
+ "Shooter / Submarine"
+ "Maze / Misc."
+ "Sports / Dodgeball"
+ "Shooter / Motorbike"
+ "Sports / Ping Pong"
+ "Misc. / Hot-air Balloon"
+ "Driving / Guide and Shoot"
+ "Sports / SkyDiving"
+ "Climbing / Mountain - Wall"
+ "Shooter / Underwater"
+ "Sports / Shuffleboard"
+ "Ball & Paddle / Misc."
+ "Sports / Swimming"
+ "Maze / Run Jump"
+ "Driving / Catch"
+ "Sports / Gun"))
+
+;; for reference
+(defparameter *all-cats*
+ '("Maze / Shooter Small"
+ "System / Device"
+ "Slot Machine / Video Slot"
+ "Sports / Football"
+ "Sports / Soccer"
+ "Game Console / Home Videogame"
+ "Electromechanical / Misc."
+ "Driving / Truck Guide"
+ "Shooter / Flying Vertical"
+ "MultiGame / Compilation"
+ "Sports / Basketball"
+ "Tabletop / Multi-Games"
+ "Sports / Baseball"
+ "Electromechanical / Pinball"
+ "Music / JukeBox"
+ "Driving / 1st Person"
+ "Shooter / Gun"
+ "Computer / Workstation - Server"
+ "Computer / Business - Terminal"
+ "Sports / Wrestling"
+ "System / BIOS"
+ "Puzzle / Match * Mature *"
+ "Shooter / Field"
+ "Casino / Cards"
+ "Misc. / Clock"
+ "Sports / Tennis"
+ "Shooter / Flying Horizontal"
+ "Puzzle / Match"
+ "Tabletop / Mahjong * Mature *"
+ "Casino / Misc."
+ "Driving / Motorbike"
+ "Maze / Collect"
+ "Fighter / 2.5D"
+ "Computer / Single Board"
+ "Utilities / EPROM Programmer"
+ "Sports / Skateboarding"
+ "Handheld / Electronic Game"
+ "Tabletop / Cards"
+ "Shooter / Misc. Horizontal"
+ "Sports / Track & Field"
+ "Sports / Pool"
+ "Misc. / Print Club"
+ "Computer / Training Board"
+ "Shooter / Gallery"
+ "Computer / Home System"
+ "Computer / Portable Digital Teletype"
+ "Quiz / Questions in Japanese"
+ "Shooter / Flying (chase view)"
+ "Platform / Run Jump"
+ "Tabletop / Hanafuda * Mature *"
+ "Computer / Construction Kit"
+ "Slot Machine / Reels"
+ "Board Game / Chess Machine"
+ "Computer / Misc."
+ "Sports / Volleyball"
+ "Driving / Race 1st Person"
+ "Utilities / Modem"
+ "Fighter / Versus"
+ "Casino / Multiplay"
+ "Platform / Shooter Scrolling"
+ "Maze / Change Surface"
+ "Driving / Race (chase view)"
+ "Slot Machine / Video Slot * Mature *"
+ "Shooter / Misc. Vertical"
+ "Coin Pusher / Misc."
+ "Puzzle / Sliding"
+ "Calculator / Pocket Computer"
+ "Computer / Development System"
+ "Tabletop / Mahjong"
+ "Shooter / Flying 1st Person"
+ "Driving / Plane"
+ "Sports / Fishing"
+ "Platform / Fighter Scrolling"
+ "Computer / Microcomputer"
+ "Sports / Golf"
+ "Music / Synthesizer"
+ "Platform / Run, Jump & Scrolling"
+ "Medal Game / Action"
+ "Misc. / Pinball"
+ "Shooter / 1st Person"
+ "Maze / Shooter Large"
+ "Shooter / 3rd Person"
+ "Sports / Bowling"
+ "Shooter / Misc."
+ "Misc. / Toy Robot"
+ "Sports / Skiing"
+ "Misc. / Multiplay"
+ "Shooter / Walking"
+ "Utilities / Test"
+ "Sports / Darts"
+ "Sports / Hockey"
+ "Maze / Outline"
+ "Telephone / Car Phone"
+ "Driving / Race Track"
+ "Climbing / Tree - Plant"
+ "Misc. / Catch"
+ "Whac-A-Mole / Hammer"
+ "Electromechanical / Redemption"
+ "Misc. / Bank-teller Terminal"
+ "Driving / Misc."
+ "Computer / Punched Car"
+ "Shooter / Flying"
+ "Shooter / Driving (chase view)"
+ "Driving / Boat"
+ "Puzzle / Drop"
+ "Puzzle / Misc."
+ "Misc. / Coin Pusher"
+ "Sports / Misc."
+ "Platform / Fighter"
+ "Fighter / 2D"
+ "Tabletop / Misc."
+ "Misc. / Electronic Board Game"
+ "Driving / Race (chase view) Bike"
+ "Ball & Paddle / Breakout"
+ "Sports / Horse Racing"
+ "Sports / Armwrestling"
+ "Misc. / Laser Disk Simulator"
+ "Sports / Boxing"
+ "Computer / Word-processing Machine"
+ "Fighter / 3D"
+ "Shooter / Driving"
+ "Calculator / Astrological Computer"
+ "Misc. / Teletype"
+ "Shooter / Versus"
+ "Shooter / Driving Vertical"
+ "Fighter / Vertical"
+ "Handheld / Pocket Device - Pad - PDA"
+ "Casino / Bingo"
+ "Puzzle / Toss"
+ "Casino / Racing"
+ "Electromechanical / Reels"
+ "Driving / Race"
+ "Board Game / Backgammon"
+ "Rhythm / Dance"
+ "Handheld / Plug n' Play TV Game"
+ "Misc. / Document Processors"
+ "Maze / Surround"
+ "Puzzle / Drop * Mature *"
+ "Board Game / Bridge Machine"
+ "Quiz / Questions in English"
+ "Misc. / Toy Cars"
+ "Multiplay / Mini-Games"
+ "Puzzle / Maze"
+ "Platform / Shooter"
+ "Maze / Escape"
+ "Casino / Lottery"
+ "Quiz / Questions in English * Mature *"
+ "Sports / Pool * Mature *"
+ "Medal Game / Bingo"
+ "Medal Game / Casino"
+ "MultiGame / Mini-Games"
+ "Handheld / Home Videogame Console"
+ "Platform / Run Jump * Mature *"
+ "Fighter / Versus * Mature *"
+ "Shooter / Driving Diagonal"
+ "Ball & Paddle / Breakout * Mature *"
+ "Ball & Paddle / Pong"
+ "Maze / Collect & Put"
+ "Rhythm / Instruments"
+ "Fighter / Misc."
+ "Misc. / Spank * Mature *"
+ "Maze / Digging"
+ "Maze / Blocks"
+ "Misc. / Device Programmer"
+ "Misc. / Cash Counter"
+ "Maze / Cross"
+ "Shooter / Driving Horizontal"
+ "Electromechanical / Bingo"
+ "Fighter / Field"
+ "Computer / Laptop - Notebook - Portable"
+ "Sports / Multiplay"
+ "Misc. / Unknown"
+ "Puzzle / Toss * Mature *"
+ "Sports / Bull Fighting"
+ "Shooter / Driving 1st Person"
+ "Casino / Cards * Mature *"
+ "Maze / Ladders"
+ "Misc. / Electronic Typewriter"
+ "Puzzle / Reconstruction"
+ "Misc. / Redemption"
+ "Maze / Driving"
+ "Handheld / Child Computer"
+ "Maze / Defeat Enemies"
+ "Ball & Paddle / Jump and Touch"
+ "Computer / Cablenet Controller"
+ "Climbing / Building"
+ "Utilities / Update"
+ "Puzzle / Outline"
+ "Computer / Child Computer"
+ "Medical Equipment / Visual Field Screener"
+ "Casino / Unknown"
+ "Board Game / Checker Machine"
+ "Misc. / Fingerprint Reader"
+ "Driving / Race Bike"
+ "Music / Audio Sequencer"
+ "Misc. / Jump and Bounce"
+ "Casino / Roulette"
+ "Shooter / Command"
+ "Sports / Sumo"
+ "Casino / Misc. * Mature *"
+ "Fighter / Compilation"
+ "Rhythm / Misc."
+ "Driving / Demolition Derby"
+ "Whac-A-Mole / Shooter"
+ "Maze / Paint"
+ "Sports / Rugby Football"
+ "Tabletop / Multiplay"
+ "Fighter / Driving Vertical"
+ "Fighter / Versus Co-op"
+ "Board Game / Dame Machine"
+ "Puzzle / Outline * Mature *"
+ "Maze / Fighter"
+ "Misc. / Satellite Receiver"
+ "Tabletop / Othello - Reversi * Mature *"
+ "Misc. / Speech Synthesizer"
+ "Misc. / Prediction"
+ "Electromechanical / Utilities"
+ "Printer / Matrix Printer"
+ "Misc. / EPROM Programmer"
+ "Misc. / Engine Control Unit"
+ "Shooter / Gallery * Mature *"
+ "Fighter / Asian 3D"
+ "Medal Game / Timing"
+ "Sports / Cards"
+ "Game Console / Fitness Game"
+ "Maze / Move and Sort"
+ "Computer Graphic Workstation / Broadcast Television"
+ "Quiz / Questions in Korean"
+ "Medal Game / Adventure"
+ "Misc. / Graphics Display Controller"
+ "Driving / Ambulance Guide"
+ "Misc. / Dartboard"
+ "Misc. / Electronic Game"
+ "Misc. / Wavetables Generator"
+ "Misc. / Car Voice Alert"
+ "Music / Instruments"
+ "Shooter / Flying * Mature *"
+ "Sports / Hang Gliding"
+ "Tabletop / Hanafuda"
+ "Puzzle / Reconstruction * Mature *"
+ "Quiz / Questions in Italian"
+ "Driving / FireTruck Guide"
+ "Utilities / Arcade System"
+ "Maze / Integrate"
+ "Whac-A-Mole / Fighter"
+ "Quiz / Questions in German"
+ "Maze / Collect * Mature *"
+ "Shooter / Flying Diagonal"
+ "Misc. / Pinball * Mature *"
+ "Tabletop / Match * Mature *"
+ "Misc. / Mini-Games"
+ "Medal Game / Horse Racing"
+ "Board Game / Cards"
+ "Tabletop / Renju"
+ "Platform / Maze"
+ "Utilities / Network Processor"
+ "Misc. / Similar Bowling Game"
+ "Whac-A-Mole / Gun"
+ "Tabletop / Go"
+ "Utilities / Redemption Board"
+ "Sports / Horseshoes"
+ "Misc. / Digital MultiMeter (DMM)"
+ "Computer / Pocket PC"
+ "Misc. / Drum Machine"
+ "Sports / Handball"
+ "Quiz / Questions in Japanese * Mature *"
+ "Telephone / ComputerPhone"
+ "Misc. / Dog Sitter"
+ "Telephone / Mobile Phone - Smartphone"
+ "Maze / Ball Guide"
+ "Misc. / Reflex"
+ "Casino / Horse Racing"
+ "Driving / Motorbike (Motocross)"
+ "Misc. / Gambling Board"
+ "Driving / Guide and Collect"
+ "Tabletop / Shougi"
+ "Misc. / Educational Game"
+ "Sports / Volley - Soccer"
+ "Misc. / Versus"
+ "Tabletop / Othello - Reversi"
+ "Fighter / Multiplay"
+ "Misc. / Response Time"
+ "Printer / Handbook"
+ "Misc. / DVD Reader-Writer"
+ "Driving / Landing"
+ "Maze / Escape * Mature *"
+ "Printer / Laser Printer"
+ "Misc. / In Circuit Emulator"
+ "Game Console / Home Videogame Console Expansion"
+ "MultiGame / Gambling"
+ "MultiGame / Gambling Board"
+ "Music / Drum Machine"
+ "Maze / Marble Madness"
+ "Quiz / Questions in Spanish"
+ "MultiGame / Compilation * Mature *"
+ "Printer / 3D Printer"
+ "Shooter / Submarine"
+ "Medical Equipment / ECG Unit"
+ "Computer / Video Production"
+ "Misc. / Time-Access Control TerminalTime and access control terminal"
+ "Maze / Misc."
+ "Printer / Thermal Printer"
+ "Multiplay / Compilation"
+ "Music / Tone Generator"
+ "Sports / Dodgeball"
+ "Calculator / Math Game Learning"
+ "Shooter / Motorbike"
+ "Medal Game / Versus"
+ "Utilities / Test ROM"
+ "Printer / Barcode Printer"
+ "Misc. / Pachinko"
+ "Puzzle / Paint * Mature *"
+ "Puzzle / Misc. * Mature *"
+ "Medal Game / Driving"
+ "Misc. / Virtual Environment"
+ "Sports / Ping Pong"
+ "Medal Game / Compilation"
+ "Misc. / VTR Control"
+ "Misc. / Hot-air Balloon"
+ "Misc. / Shoot Photos"
+ "Puzzle / Sliding * Mature *"
+ "Maze / Blocks * Mature *"
+ "Quiz / Questions in Chinese"
+ "Driving / Guide and Shoot"
+ "Sports / SkyDiving"
+ "Climbing / Mountain - Wall"
+ "Shooter / Outline * Mature *"
+ "Multiplay / Cards"
+ "Quiz / Questions in French"
+ "Puzzle / Cards"
+ "Misc. / Portable Media Player"
+ "Utilities / Weight Scale"
+ "Electromechanical / Change Money"
+ "Misc. / Graphic Tablet"
+ "Shooter / Flying Horizontal * Mature *"
+ "Misc. / Robot Control"
+ "Shooter / Underwater"
+ "Sports / Shuffleboard"
+ "Misc. / Temperature Controller"
+ "Misc. / Laserdisc Simulator"
+ "Casino / Multi-Games"
+ "Ball & Paddle / Misc."
+ "Utilities / Arcade Switcher"
+ "Shooter / Flying Vertical * Mature *"
+ "Calculator / Talking Calculator"
+ "Computer / Educational Game"
+ "Misc. / DVD Player"
+ "Maze / Digging * Mature *"
+ "Whac-A-Mole / Footsteps"
+ "Handheld / E-Book Reading"
+ "Multiplay / Misc. * Mature *"
+ "Sports / Swimming"
+ "Computer / Milling"
+ "Misc. / Order"
+ "Misc. / Dot-Matrix Display"
+ "Medal Game / Cards"
+ "Music / MIDI Player"
+ "Maze / Run Jump"
+ "Utilities / Electronic Digital Thermostat"
+ "Utilities / TV Test Pattern Generator"
+ "Misc. / Credit Card Terminal"
+ "Multiplay / Mini-Games * Mature *"
+ "Music / Player"
+ "Misc. / Laserdisc Player"
+ "Driving / Catch"
+ "Sports / Gun"
+ "Computer / Programming Machine"
+ "Medal Game / Dance"
+ "Handheld / Handpuppet Toy"))
--- /dev/null
+#!/usr/bin/python3
+# minidsp-lcd-monitor.py --- A simple LCD monitor for the minidsp 2x4HD
+
+# Copyright (C) 2022 Clinton Ebadi <clinton@unknownlamer.org>
+
+# Author: Clinton Ebadi <clinton@unknownlamer.org>
+
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+# Hardware needed:
+#
+# * Adafruit USB+Serial RGB Backlight Character LCD Backpack
+# <https://learn.adafruit.com/usb-plus-serial-backpack/overview>
+# <https://learn.adafruit.com/usb-plus-serial-backpack/command-reference>
+# (hard requirement is a 20x4 character lcd with 5x8 cells)
+# * Adafruit 20x4 RGB Character LCD (Negative looks cooler but isn't required)
+# <https://www.adafruit.com/product/498>
+#
+# Software dependencies:
+#
+# * minidsp-rs daemon with HTTP API enabled
+# <https://minidsp-rs.pages.dev/daemon/http>
+
+# TODO (probably for someone else, patches welcome):
+# - Configuration file
+# - Support different sizes of LCDs or at least controlling which data
+# is shown and on which line.
+# - More consistent naming scheme for LCDDriver methods
+# - Remove extra pip from bottom of volume bar? Actual characters
+# don't use it so might look better that way.
+
+import asyncio
+import json
+import pprint
+import serial
+import sys
+import time
+import websockets
+
+# Configuration
+LCD_ROWS = 4
+LCD_COLS = 20
+LCD_CONTRAST = 255
+LCD_BRIGHTNESS = 200
+
+SERIAL_PORT = '/dev/ttyACM0'
+
+MINIDSP_WS = 'ws://127.0.0.1:5380/devices/0'
+
+# LCD control
+class LCDDriver:
+ def __init__(self, port = SERIAL_PORT, cols = LCD_COLS, rows = LCD_ROWS, contrast = LCD_CONTRAST, brightness = LCD_BRIGHTNESS):
+ self.lcd_rows = rows
+ self.lcd_cols = cols
+ self.lcd_contrast = contrast
+ self.lcd_brightness = brightness
+ self.serio = serial.Serial(port, 9600, timeout=1)
+
+ #self.cmd_set_display_size(20, 4)
+ self.cmd_clear_screen ()
+ self.cmd_set_contrast (contrast)
+ self.cmd_set_brightness(brightness)
+ self.cmd_backlight_enable ()
+
+ # Low level command sending, use cmd_FOO functions instead
+ def send_command(self, codes):
+ self.serio.write(b'\xFE')
+ self.serio.write(bytes(codes))
+
+ # high level interface
+ def write(self, txt):
+ self.serio.write(txt)
+
+ # https://learn.adafruit.com/usb-plus-serial-backpack/command-reference
+ def cmd_backlight_enable(self, state=True):
+ if state:
+ # Adafruit backpack doesn't support timeout
+ self.send_command([0x42, 0])
+ else:
+ self.send_command([0x46])
+
+ def cmd_autoscroll(self, enable=True):
+ if enable:
+ self.send_command(b'\x51')
+ else:
+ self.send_command(b'\x52')
+
+ def cmd_set_brightness(self, brightness):
+ self.send_command([0x98, brightness])
+ self.lcd_brightness = brightness
+
+ def cmd_set_backlight_color(self, r, g, b):
+ self.send_command([0xD0, r, g, b])
+
+ def cmd_set_display_size(self, cols, rows):
+ self.send_command([0xD1, cols, rows])
+
+ def cmd_set_contrast(self, contrast):
+ self.send_command([0x50, contrast])
+ self.lcd_contrast = contrast
+
+ def cmd_clear_screen (self):
+ self.send_command(b'\x58')
+
+ def cmd_cursor_set_position(self, col, row):
+ self.send_command([0x47, col, row])
+
+ def cmd_cursor_home(self):
+ self.send_command(b'\x48')
+
+ def cmd_cursor_forward(self):
+ self.send_command(b'\x4A')
+
+ def cmd_cursor_back(self):
+ self.send_command(b'\x4B')
+
+ def cmd_character_set(self, slot, chars):
+ self.send_command([0x4E, slot] + chars)
+
+class DSPMonitor:
+ # Volume bar characters
+ #
+ # Bars are set every other character column so that they appear even
+ # despite gaps between characters. Using 17 characters and making each
+ # visible bar a 2% increment gives us a max of 102%.
+ custom_bar_1 = [0x0,0x10,0x10,0x10,0x10,0x10,0x10,0x0]
+ custom_bar_2 = [0x0,0x14,0x14,0x14,0x14,0x14,0x14,0x0]
+ custom_bar_3 = [0x0,0x15,0x15,0x15,0x15,0x15,0x15,0x0]
+ custom_dB = [0x6,0x5,0x5,0x6,0xd,0x15,0x15,0x1e] # dB, kind of...
+
+ minidsp_min_volume_db = -127
+
+ def __init__(self, wsurl = MINIDSP_WS):
+ self.wsurl = wsurl
+ self.dspattrs = { 'preset': None, 'source': None, 'volume': None, 'mute': None }
+ self.lcd = LCDDriver ()
+
+ self.lcd.cmd_character_set(0, self.custom_bar_1)
+ self.lcd.cmd_character_set(1, self.custom_bar_2)
+ self.lcd.cmd_character_set(2, self.custom_bar_3)
+ self.lcd.cmd_character_set(3, self.custom_dB)
+
+ self.lcd.cmd_autoscroll(False)
+ self.lcd.cmd_set_backlight_color(255, 0, 100)
+ self.lcd.cmd_cursor_home()
+ self.lcd.write(b'DSP Monitor Ready')
+
+ async def run(self):
+ self.lcd.cmd_clear_screen()
+ self.lcd.cmd_cursor_home()
+ self.lcd.write(b'DSP Monitor Connecting...')
+ # pings disabled as minidsp-rs does not appear to support them
+ async for websocket in websockets.connect(self.wsurl, ping_interval=None):
+ try:
+ force_redisplay = True
+ self.lcd.cmd_clear_screen()
+ self.lcd.cmd_cursor_home()
+ async for message in websocket:
+ await self.process_ws_message(message, force_redisplay)
+ force_redisplay = False
+ except websockets.ConnectionClosed:
+ self.lcd.cmd_clear_screen()
+ self.lcd.cmd_cursor_home()
+ self.lcd.write(b'Connection Lost'.center(20))
+ time.sleep(0.5)
+ continue
+
+ async def process_ws_message(self,message, force_redisplay=False):
+ print (message);
+ parsed = json.loads(message)
+ pprint.pp (parsed['master'])
+ self.update_screen(self.dspattrs | parsed['master'], force=force_redisplay)
+
+ def update_screen(self, attrs=None, force=False):
+ if attrs == None:
+ attrs = self.dspattrs
+ if attrs['source'] != self.dspattrs['source'] or force:
+ self.draw_source(attrs['source'])
+ if attrs['preset'] != self.dspattrs['preset'] or force:
+ self.draw_preset(attrs['preset'])
+ if attrs['volume'] != self.dspattrs['volume'] or attrs['mute'] != self.dspattrs['mute'] or force:
+ pct = int(abs((self.minidsp_min_volume_db - attrs['volume']) / abs(self.minidsp_min_volume_db)) * 100)
+ print('pct {}, min {}, vol {}'.format(pct, self.minidsp_min_volume_db, attrs['volume']))
+ self.draw_volume_bar (pct, attrs['mute'])
+
+ self.dspattrs = attrs
+
+ def draw_source(self, source_name):
+ self.lcd.cmd_cursor_set_position(1,1)
+ self.lcd.write(b'SOURCE: ')
+ self.lcd.write(bytes(source_name.ljust(12), 'ascii'))
+
+ def draw_preset(self, preset_number):
+ self.lcd.cmd_cursor_set_position(1, 2)
+ self.lcd.write(b'PRESET: ')
+ self.lcd.write(bytes(str(preset_number + 1).ljust(12), 'ascii'))
+
+ def draw_volume_bar(self, percentage, muted):
+ full_blocks = int(percentage / 6)
+ partial_ticks = int(percentage % 6 / 2)
+
+ #self.lcd.cmd_cursor_set_position(1, 3)
+ #self.lcd.write(b'VOLUME:')
+
+ #self.lcd.cmd_cursor_set_position(1, 4)
+ if muted:
+ self.lcd.cmd_cursor_set_position(1, 3)
+ self.lcd.write(b'MUTED'.center(20))
+ self.lcd.cmd_cursor_set_position(1, 4)
+ self.lcd.write(b' '.center(20))
+ else:
+ for row in [3, 4]:
+ self.lcd.cmd_cursor_set_position(1, row)
+ for i in range(full_blocks):
+ self.lcd.write([2])
+
+ if partial_ticks == 2:
+ self.lcd.write([1])
+ elif partial_ticks == 1:
+ self.lcd.write([0])
+ else:
+ self.lcd.write(b' ')
+
+ for i in range(18 - full_blocks - 1):
+ self.lcd.write(b' ')
+
+ self.lcd.cmd_cursor_set_position(18, 4)
+ if percentage == 0:
+ self.lcd.write (b'min')
+ elif percentage == 100:
+ self.lcd.write (b'max')
+ else:
+ self.lcd.write (bytes(f'{percentage:2d}%', 'ascii'))
+
+dspmon = DSPMonitor()
+asyncio.run(dspmon.run())