A lot more robust, status display working.
authorTim Vaughan <timv@ughan.xyz>
Sat, 7 Dec 2019 21:00:12 +0000 (22:00 +0100)
committerTim Vaughan <timv@ughan.xyz>
Sat, 7 Dec 2019 21:00:12 +0000 (22:00 +0100)
emus.el

diff --git a/emus.el b/emus.el
index eb3d566..b03c09d 100644 (file)
--- a/emus.el
+++ b/emus.el
   "Name of (and, optionally, path to) mpg123 binary."
   :type '(string))
 
+(defface emus-artist
+  '((t :inherit font-lock-keyword-face))
+  "Face used for artist names in browser.")
+
+(defface emus-album
+  '((t :inherit font-lock-function-name-face))
+  "Face used for album names in browser.")
+
+(defface emus-title
+  '((t :inherit font-lock-string-face))
+  "Face used for track titles in browser.")
+
 ;;; Library
 ;;
 
-(defvar emus-library nil
-  "Emus audio library.")
-
 (defun emus-get-audio-files ()
   "Get all mp3 files in main emus directory."
   (directory-files-recursively emus-directory ".*\\.mp3"))
 
-(defvar emus-records nil)
+(defvar emus-records nil
+  "Emus audio library.")
 
 (defun emus-make-record (filename tagstr)
   (let ((artist "")
@@ -51,7 +61,7 @@
          (found-artist (setq artist found-artist))
          (found-album (setq album found-album))
          (found-title (setq title found-title)))))
-    (list artist album title filename)))
+    (vector artist album title filename nil)))
 
 (defun emus-record-artist (record)
   (elt record 0))
 (defun emus-record-file (record)
   (elt record 3))
 
-(defun emus-update-records (then)
+(defun emus-record-browser-pos (record)
+  (elt record 4))
+
+(defun emus-set-record-browser-pos (record pos)
+  (aset record 4 pos))
+
+(defun emus-update-records ()
+  (interactive)
+  (emus--suspend-cp)
+  (setq emus-state 'stopped)
   (let ((proc (emus-get-process))
         (tagstr "")
         (filenames (emus-get-audio-files)))
                                  (if filenames
                                      (emus-send-cmd "lp" (car filenames))
                                    (set-process-filter proc nil)
-                                   (funcall then)))))
+                                   (setq emus-records (reverse emus-records))
+                                   (emus-sort-records)
+                                   (emus-render-records)
+                                   (emus--resume-cp)))))
     (emus-send-cmd "lp" (car filenames))))
 
+(defun emus-sort-records ()
+  (sort emus-records
+        (lambda (r1 r2)
+          (let ((artist1 (emus-record-artist r1))
+                (artist2 (emus-record-artist r2)))
+            (if (string= artist1 artist2)
+                (let ((album1 (emus-record-album r1))
+                      (album2 (emus-record-album r2)))
+                  (string< album1 album2))
+              (string< artist1 artist2))))))        
+
 ;;; mpg123 process
 ;;
 
                              nil))))
     (if emus-process
         emus-process
-      (make-process :name "emus-process"
-                    ;; :buffer (get-buffer-create "*emus-process*")
-                    :command `(,emus-mpg123-program "-R")))))
+      (let ((proc
+             (make-process :name "emus-process"
+                           ;; :buffer (get-buffer-create "*emus-process*")
+                           :command `(,emus-mpg123-program "-R"))))
+        (process-send-string proc "silence\n")
+        proc))))
+                    
 
 (defun emus-send-cmd (cmd &rest args)
   (process-send-string (emus-get-process)
                         (seq-reduce (lambda (s1 s2) (concat s1 " " s2)) args cmd)
                         "\n")))
 
+(defun emus-send-and-process (respfun predfun cmd &rest args)
+  (let ((respstr ""))
+    (set-process-filter (emus-get-process)
+                        (lambda (proc string)
+                          (setq respstr (concat respstr string))
+                          (when (funcall predfun respstr)
+                            (set-process-filter proc nil)
+                            (funcall respfun respstr))))
+    (apply #'emus-send-cmd cmd args)))
+
+
 ;;; Playback
 ;;
 
-(defvar emus-currently-playing nil)
+(defvar emus-current-record nil)
+(defvar emus-state 'stopped)
+(defvar emus-continuous-playback t)
+
+(defun emus--suspend-cp ()
+  (setq emus-continuous-playback nil))
+
+(defun emus--resume-cp ()
+  (setq emus-continuous-playback t)
+  (set-process-filter (emus-get-process)
+                      (lambda (proc string)
+                        (and emus-continuous-playback
+                             (eq emus-state 'playing)
+                             (string-suffix-p "@P 0\n" string)
+                             (emus-play-next)))))
 
 (defun emus-play-record (record)
-  (setq emus-currently-playing record)
-  (emus-send-cmd "l" (emus-record-file record)))
+  (let ((old-record emus-current-record))
+    (emus-send-cmd "l" (emus-record-file record))
+    (setq emus-state 'playing)
+    (setq emus-current-record record)
+    (emus-update-record old-record)
+    (emus-update-record record)
+    (emus--resume-cp)))
 
 (defun emus-stop ()
   (interactive)
-  (emus-send-cmd "s")
-  (setq emus-currently-playing nil))
+  (setq emus-state 'stopped)
+  (emus-update-record emus-current-record)
+  (emus-send-cmd "s"))
 
 (defun emus-playpause ()
   (interactive)
-  (emus-send-cmd "p"))
-
-
-(defun emus-volume (pct)
+  (when emus-current-record
+    (if (eq emus-state 'stopped)
+        (emus-play-record emus-current-record)
+      (emus-send-cmd "p")
+      (pcase emus-state
+        ((or 'paused 'stopped) (setq emus-state 'playing))
+        ('playing (setq emus-state 'paused)))
+      (unless (eq emus-state 'paused)))
+    (emus-update-record emus-current-record)))
+
+(defun emus-set-volume (pct)
   (emus-send-cmd "v" (number-to-string pct)))
 
-(defvar emus-current-volume 10)
+(defvar emus-current-volume 100)
 
 (defun emus-volume-delta (delta)
   (setq emus-current-volume (max 0 (min 100 (+ emus-current-volume delta))))
-  (emus-volume emus-current-volume))
+  (emus-set-volume emus-current-volume))
 
 (defun emus-volume-up ()
   (interactive)
   (interactive)
   (emus-volume-delta -10))
 
+(defun emus--play-nearby (offset)
+  (let ((idx (seq-position emus-records emus-current-record)))
+    (if idx
+        (let ((next-record (elt emus-records (+ idx offset))))
+          (if next-record
+              (emus-play-record next-record)
+            (error "Track does not exist")))
+      (error "No track is currently selected."))))
+
+(defun emus-play-next ()
+  (interactive)
+  (emus--play-nearby 1))
+
+(defun emus-play-prev ()
+  (interactive)
+  (emus--play-nearby -1))
+
+(defun emus-display-status ()
+  (interactive)
+  (message
+   (concat "Emus: Volume %d%%"
+           (pcase emus-state
+             ('stopped " [Stopped]")
+             ('paused " [Paused]")
+             ('playing " [Playing]")
+             (_ ""))
+           (if emus-current-record
+               (format " - %.30s (%.20s)"
+                       (emus-record-title emus-current-record)
+                       (emus-record-artist emus-current-record))
+             ""))
+   emus-current-volume))
+
 
 ;;; Browser
 ;;
 
-(defun emus-render-record (record)
+(defun emus-insert-record (record &optional pref-record)
+  (emus-set-record-browser-pos record (point))
+  (let ((current (equal record emus-current-record)))
    (insert-text-button
     (concat
+     (if current
+         (pcase emus-state
+           ('playing (propertize ">" 'face 'bold))
+           ('paused (propertize ")" 'face 'bold))
+           ('stopped (propertize "]" 'face 'bold)))
+       (propertize " " 'face 'default))
      (propertize (format "%-20.20s" (emus-record-artist record))
-                 'face 'font-lock-keyword-face)
+                 'face 'emus-artist)
      (propertize (format "%  -20.20s" (emus-record-album record))
-                 'face 'font-lock-function-name-face)
+                 'face 'emus-album)
      (propertize (format "  %s" (emus-record-title record))
-                 'face 'font-lock-string-face))
+                 'face 'emus-title))
     'action #'emus-click-record
     'follow-link t
-    'emus-record record)
+    'emus-record record))
   (insert "\n"))
 
+(defun emus-update-record (record)
+  (let ((record-pos (emus-record-browser-pos record)))
+    (when (and (get-buffer "*emus*")
+               (emus-record-browser-pos record))
+      (with-current-buffer "*emus*"
+        (let ((inhibit-read-only t)
+              (old-point (point)))
+            (goto-char record-pos)
+            (search-forward "\n")
+            (delete-region record-pos (point))
+            (goto-char record-pos)
+            (emus-insert-record record)
+            (goto-char old-point))))))
+
 (defun emus-render-records ()
-  (with-current-buffer "*emus*"
-    (let ((inhibit-read-only t))
-      (save-excursion
+  (save-mark-and-excursion
+    (with-current-buffer "*emus*"
+      (let ((inhibit-read-only t))
         (erase-buffer)
         (goto-char (point-min))
         (dolist (record emus-records)
-          (emus-render-record record))))))
+          (emus-insert-record record))))))
 
 (defun emus-click-record (button)
   (emus-play-record (button-get button 'emus-record)))
   "Switch to *emus* audio library browser."
   (interactive)
   (switch-to-buffer "*emus*")
-  (emus-mode)
+  (emus-browser-mode)
   (emus-volume emus-current-volume)
   (if emus-records
       (emus-render-records)
-    (emus-update-records #'emus-render-records)))
+    (emus-update-records)))
 
-(defvar emus-mode-map
+(defvar emus-browser-mode-map
   (let ((map (make-sparse-keymap)))
     (define-key map (kbd "SPC") 'emus-playpause)
     (define-key map (kbd "o") 'emus-stop)
     (define-key map (kbd "+") 'emus-volume-up)
     (define-key map (kbd "=") 'emus-volume-up)
     (define-key map (kbd "-") 'emus-volume-down)
+    (define-key map (kbd "R") 'emus-update-records)
+    (define-key map (kbd "n") 'emus-play-next)
+    (define-key map (kbd "p") 'emus-play-prev)
     (when (fboundp 'evil-define-key*)
       (evil-define-key* 'motion map
         (kbd "SPC") 'emus-playpause
         (kbd "o") 'emus-stop
         (kbd "+") 'emus-volume-up
         (kbd "=") 'emus-volume-up
-        (kbd "-") 'emus-volume-down))
+        (kbd "-") 'emus-volume-down
+        (kbd "R") 'emus-update-records
+        (kbd "n") 'emus-play-next
+        (kbd "p") 'emus-play-prev))
     map)
   "Keymap for emus.")
 
-(define-derived-mode emus-mode special-mode "Emus"
+(define-derived-mode emus-browser-mode special-mode "emus-browser"
   "Major mode for EMUS music player.")
 
 (when (fboundp 'evil-set-initial-state)
-  (evil-set-initial-state 'emus-mode 'motion))
+  (evil-set-initial-state 'emus-browser-mode 'motion))
 
 ;;; Debugging