X-Git-Url: https://thelambdalab.xyz/gitweb/index.cgi?p=emus.git;a=blobdiff_plain;f=emus.el;h=2c879c46b541db102bf863ea164a91ff1e253baf;hp=71c6bd29a594eaea453e1f75490af0f2d01fed1a;hb=869c7b36b90740b4ac744270ef38d20226f2c391;hpb=d917ede8adcc0e4f78f557f95e42d5388551c834 diff --git a/emus.el b/emus.el index 71c6bd2..2c879c4 100644 --- a/emus.el +++ b/emus.el @@ -41,13 +41,6 @@ (require 'seq) -;;; Global constants -;; - -(defconst emus-version "1.0.0" - "Current version of emus.") - - ;;; Customizations ;; @@ -83,6 +76,66 @@ '((t :inherit bold)) "Face used for current track cursor") + +;;; Global variables + +(defvar emus--proc-in-use nil + "If non-nil, disables `emus-send-cmd'. +Used to prevent commands from interfering with library construction.") + +(defvar emus-tracks nil + "Emus audio library.") + +(defvar emus-current-track nil + "Currently-selected emus track.") + +(defvar emus-state 'stopped + "Current playback state.") + +(defvar emus-continuous-playback t + "If non-nil, emus will automatically play the next track when the current track is finished.") + +(defvar emus-current-volume 100 + "The current playback volume.") + + +;;; mpg123 process +;; + + +(defun emus-get-process () + "Return current or new mpg123 process." + (let* ((emus-process-raw (get-process "emus-process")) + (emus-process (if emus-process-raw + (if (process-live-p emus-process-raw) + emus-process-raw + (kill-process emus-process-raw) + nil)))) + (if emus-process + emus-process + (let ((proc + (make-process :name "emus-process" + ;; :buffer (get-buffer-create "*emus-process*") + :command `(,emus-mpg123-program "-R")))) + (set-process-query-on-exit-flag proc nil) + (process-send-string proc "silence\n") + proc)))) + +(defun emus--send-cmd-raw (cmd &rest args) + "Send a command CMD with args ARGS to the mpg123 process. +This procedure does not respect `emus--proc-in-use' and thus should only +be used by `emus--load-library'." + (process-send-string (emus-get-process) + (concat + (seq-reduce (lambda (s1 s2) (concat s1 " " s2)) args cmd) + "\n"))) + +(defun emus-send-cmd (cmd &rest args) + "Send a command CMD with args ARGS to the mpg123 process." + (unless emus--proc-in-use + (apply #'emus--send-cmd-raw cmd args))) + + ;;; Library ;; @@ -90,58 +143,71 @@ "Get all mp3 files in main emus directory." (directory-files-recursively emus-directory ".*\\.mp3")) -(defvar emus-tracks nil - "Emus audio library.") - (defun emus-make-track (artist album title filename &optional pos) + "Create an object representing an emus track. +ARTIST, ALBUM and TITLE are used to describe the track, FILENAME +refers to the mp3 file containing the track. If non-nil, POS +specifies the position of the record representing this track in the +emus browser buffer." (vector artist album title filename pos)) (defun emus-track-artist (track) + "The artist corresponding to TRACK." (elt track 0)) (defun emus-track-album (track) + "The album corresponding to TRACK." (elt track 1)) (defun emus-track-title (track) + "The title of TRACK." (elt track 2)) (defun emus-track-file (track) + "The mp3 file corresponding to TRACK." (elt track 3)) (defun emus-track-browser-pos (track) + "The location of the browser buffer record corresponding to TRACK." (elt track 4)) (defun emus-set-track-browser-pos (track pos) + "Set the location of the browser buffer record corresponding to TRACK to POS." (aset track 4 pos)) (defun emus--load-library (then) - (emus--suspend-cp) - (setq emus-state 'stopped) - (let ((proc (emus-get-process)) - (tagstr "") - (filenames (emus-get-audio-files))) - (setq emus-tracks nil) - (set-process-filter proc (lambda (proc string) - (setq tagstr (concat tagstr string)) - (when (string-suffix-p "@P 1\n" string) - (add-to-list 'emus-tracks - (emus--make-track-from-tagstr (car filenames) - tagstr)) - (setq tagstr "") - (setq filenames (cdr filenames)) - (if filenames - (emus-send-cmd "lp" (car filenames)) - (set-process-filter proc nil) - (setq emus-tracks (reverse emus-tracks)) - (emus--sort-tracks) - (unless emus-current-track - (setq emus-current-track (car emus-tracks))) - (funcall then) - ;; (emus-render-tracks) - (emus--resume-cp))))) - (emus-send-cmd "lp" (car filenames)))) + "Initialize the emus track library. +Once the library is initialized, the function THEN is called." + (unless emus--proc-in-use + (setq emus--proc-in-use t) + (emus--suspend-cp) + (setq emus-state 'stopped) + (let ((proc (emus-get-process)) + (tagstr "") + (filenames (emus-get-audio-files))) + (setq emus-tracks nil) + (set-process-filter proc (lambda (proc string) + (setq tagstr (concat tagstr string)) + (when (string-suffix-p "@P 1\n" string) + (add-to-list 'emus-tracks + (emus--make-track-from-tagstr (car filenames) + tagstr)) + (setq tagstr "") + (setq filenames (cdr filenames)) + (if filenames + (emus--send-cmd-raw "lp" (car filenames)) + (set-process-filter proc nil) + (setq emus-tracks (reverse emus-tracks)) + (emus--sort-tracks) + (unless emus-current-track + (setq emus-current-track (car emus-tracks))) + (funcall then) + (emus--resume-cp) + (setq emus--proc-in-use nil))))) + (emus--send-cmd-raw "lp" (car filenames))))) (defun emus--make-track-from-tagstr (filename tagstr) + "Parse TAGSTR to populate the fields of a track corresponding to FILENAME." (let ((artist "") (album "") (title "")) @@ -156,6 +222,9 @@ (emus-make-track artist album title filename nil))) (defun emus--sort-tracks () + "Sort the library tracks according to artist and album. +Leaves the track titles unsorted, so they will appear in the order specified +by the filesystem." (sort emus-tracks (lambda (r1 r2) (let ((artist1 (emus-track-artist r1)) @@ -166,67 +235,26 @@ (string< album1 album2)) (string< artist1 artist2)))))) -(defmacro emus--with-library (&rest args) +(defmacro emus--with-library (&rest body) + "Evaluate BODY with the library initialized." `(if emus-tracks - (progn ,@args) + (progn ,@body) (emus--load-library - (lambda () ,@args)))) - -;;; mpg123 process -;; - -(defvar emus-proc-in-use nil) - -(defun emus-get-process () - "Return current or new mpg123 process." - (let* ((emus-process-raw (get-process "emus-process")) - (emus-process (if emus-process-raw - (if (process-live-p emus-process-raw) - emus-process-raw - (kill-process emus-process-raw) - nil)))) - (if emus-process - emus-process - (let ((proc - (make-process :name "emus-process" - ;; :buffer (get-buffer-create "*emus-process*") - :command `(,emus-mpg123-program "-R")))) - (set-process-query-on-exit-flag proc nil) - (process-send-string proc "silence\n") - proc)))) - - -(defun emus-send-cmd (cmd &rest args) - (process-send-string (emus-get-process) - (concat - (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))) + (lambda () ,@body)))) ;;; Playback ;; -(defvar emus-current-track nil) -(defvar emus-state 'stopped) -(defvar emus-continuous-playback t) - (defun emus--suspend-cp () + "Suspend continuous playback." (setq emus-continuous-playback nil)) (defun emus--resume-cp () + "Resume continuous playback." (setq emus-continuous-playback t) (set-process-filter (emus-get-process) - (lambda (proc string) + (lambda (_proc string) (and emus-continuous-playback (eq emus-state 'playing) (string-suffix-p "@P 0\n" string) @@ -241,7 +269,8 @@ (setq emus-current-track track) (emus--update-track old-track) (emus--update-track track) - (emus--resume-cp)))) + (emus--resume-cp) + (emus-goto-current)))) (defun emus-select-track (track) "Set TRACK as current, but do not start playing." @@ -252,7 +281,8 @@ (emus--update-track old-track) (emus--update-track track) (emus-send-cmd "o") - (emus--resume-cp)))) + (emus--resume-cp) + (emus-goto-current)))) (defun emus-stop () "Stop playback of the current track." @@ -263,6 +293,9 @@ (emus-send-cmd "s"))) (defun emus-playpause () + "Begin playback of the current track. +If the track is already playing, pause playback. +If the track is currently paused, resume playback." (interactive) (emus--with-library (when emus-current-track @@ -275,25 +308,28 @@ (unless (eq emus-state 'paused))) (emus--update-track emus-current-track)))) -(defvar emus-current-volume 100) - (defun emus-set-volume (pct) + "Set the playback volume to PCT %." (emus--with-library (setq emus-current-volume pct) (emus-send-cmd "v" (number-to-string pct)))) (defun emus-volume-increase-by (delta) + "Increase the playback volume by DELTA %." (emus-set-volume (max 0 (min 100 (+ emus-current-volume delta))))) (defun emus-volume-up () + "Increase the playback volume by 10%." (interactive) (emus-volume-increase-by 10)) (defun emus-volume-down () + "Decrease the playback volume by 10%." (interactive) (emus-volume-increase-by -10)) (defun emus--play-adjacent-track (&optional prev) + "Play the next track in the library, or the previous if PREV is non-nil." (emus--with-library (let ((idx (seq-position emus-tracks emus-current-track)) (offset (if prev -1 +1))) @@ -307,6 +343,8 @@ (error "No track selected"))))) (defun emus--play-adjacent-album (&optional prev) + "Play the first track of the next album in the library. +If PREV is non-nil, plays the last track of the previous album." (emus--with-library (let ((idx (seq-position emus-tracks emus-current-track))) (if idx @@ -328,18 +366,22 @@ (error "No track selected"))))) (defun emus-play-next () + "Play the next track in the library." (interactive) (emus--play-adjacent-track)) (defun emus-play-prev () + "Play the previous track in the library." (interactive) (emus--play-adjacent-track t)) (defun emus-play-next-album () + "Play the first track of the next album in the library." (interactive) (emus--play-adjacent-album)) (defun emus-play-prev-album () + "Play the last track of the previous album in the library." (interactive) (emus--play-adjacent-album t)) @@ -359,6 +401,7 @@ (emus-jump -10)) (defun emus-display-status () + "Display the current playback status in the minibuffer." (interactive) (emus--with-library (message @@ -380,6 +423,13 @@ ;; (defun emus--insert-track (track &optional prev-track first) + "Insert a button representing TRACK into the current buffer. + +When provided, PREV-TRACK is used to determine whether to insert additional +headers representing the artist or the album title. + +If non-nil, FIRST indicates that the track is the first in the library +and thus requires both artist and album headers." (let* ((artist (emus-track-artist track)) (album (emus-track-album track)) (title (emus-track-title track)) @@ -427,6 +477,8 @@ 'emus-track)))))) (defun emus--update-track (track) + "Rerender entry for TRACK in emus browser buffer. +Used to update browser display when `emus-current-track' and/or `emus-state' changes." (let ((track-pos (emus-track-browser-pos track))) (when (and (get-buffer "*emus*") (emus-track-browser-pos track)) @@ -441,6 +493,7 @@ (goto-char old-point)))))) (defun emus--render-tracks () + "Render all library tracks in emus browser buffer." (with-current-buffer "*emus*" (let ((inhibit-read-only t) (old-pos (point))) @@ -453,44 +506,106 @@ (goto-char old-pos)))) (defun emus--click-track (button) + "Begin playback of track indicated by BUTTON." (emus-play-track (button-get button 'emus-track)) (emus-display-status)) -(defun emus-centre-current () +(defun emus-goto-current () + "Move point to the current track in the browser buffer, if available." (interactive) - (when (get-buffer "*emus*") - (when emus-current-track - (goto-char (emus-track-browser-pos emus-current-track)) - (recenter)))) + (when (and (get-buffer "*emus*") + emus-current-track) + (with-current-buffer "*emus*" + (goto-char (emus-track-browser-pos emus-current-track))))) (defun emus-browse () "Switch to *emus* audio library browser." (interactive) (emus--with-library - (switch-to-buffer "*emus*") + (pop-to-buffer "*emus*") (emus-browser-mode) (emus--render-tracks) - (emus-centre-current))) + (emus-goto-current))) (defun emus-refresh () + "Refresh the emus library." (interactive) (emus-stop) (setq emus-tracks nil) (emus-browse)) -(defun emus-playpause-status () (interactive) (emus-playpause) (emus-display-status)) -(defun emus-stop-status () (interactive) (emus-stop) (emus-display-status)) -(defun emus-volume-up-status () (interactive) (emus-volume-up) (emus-display-status)) -(defun emus-volume-down-status () (interactive) (emus-volume-down) (emus-display-status)) -(defun emus-play-next-status () (interactive) (emus-play-next) (emus-display-status)) -(defun emus-play-prev-status () (interactive) (emus-play-prev) (emus-display-status)) -(defun emus-play-next-album-status () (interactive) (emus-play-next-album) (emus-display-status)) -(defun emus-play-prev-album-status () (interactive) (emus-play-prev-album) (emus-display-status)) -(defun emus-jump-10s-forward-status () (interactive) (emus-jump-10s-forward) (emus-display-status)) -(defun emus-jump-10s-backward-status () (interactive) (emus-jump-10s-backward) (emus-display-status)) -(defun emus-centre-current-status () (interactive) (emus-centre-current) (emus-display-status)) + +;;; Playback + status display commands +;; + +(defun emus-playpause-status () + "Start, pause or resume playback, then display the emus status in the minibuffer." + (interactive) + (emus-playpause) + (emus-display-status)) + +(defun emus-stop-status () + "Stop playback, then display the emus status in the minibuffer." + (interactive) + (emus-stop) + (emus-display-status)) + +(defun emus-volume-up-status () + "Increase volume by 10%, then display the emus status in the minibuffer." + (interactive) + (emus-volume-up) + (emus-display-status)) + +(defun emus-volume-down-status () + "Decrease volume by 10%, then display the emus status in the minibuffer." + (interactive) + (emus-volume-down) + (emus-display-status)) + +(defun emus-play-next-status () + "Play next track, then display the emus status in the minibuffer." + (interactive) + (emus-play-next) + (emus-display-status)) + +(defun emus-play-prev-status () + "Play previous track, then display the emus status in the minibuffer." + (interactive) + (emus-play-prev) + (emus-display-status)) + +(defun emus-play-next-album-status () + "Play first track of next album, then display the emus status in the minibuffer." + (interactive) + (emus-play-next-album) + (emus-display-status)) + +(defun emus-play-prev-album-status () + "Play last track of previous album, then display the emus status in the minibuffer." + (interactive) + (emus-play-prev-album) + (emus-display-status)) + +(defun emus-jump-10s-forward-status () + "Jump 10s forward in current track, then display the emus status in the minibuffer." + (interactive) + (emus-jump-10s-forward) + (emus-display-status)) + +(defun emus-jump-10s-backward-status () + "Jump 10s backward in current track, then display the emus status in the minibuffer." + (interactive) + (emus-jump-10s-backward) + (emus-display-status)) + +(defun emus-goto-current-status () + "Move point to the current track, then display the emus status in the minibuffer." + (interactive) + (emus-goto-current) + (emus-display-status)) (defun emus-refresh-status () + "Refresh the emus library, then display the emus status in the minibuffer." (interactive) (emus-stop) (setq emus-tracks nil) @@ -512,7 +627,7 @@ (define-key map (kbd "P") 'emus-play-prev-album-status) (define-key map (kbd ",") 'emus-jump-10s-backward-status) (define-key map (kbd ".") 'emus-jump-10s-forward-status) - (define-key map (kbd "c") 'emus-centre-current-status) + (define-key map (kbd "c") 'emus-goto-current-status) (when (fboundp 'evil-define-key*) (evil-define-key* 'motion map (kbd "SPC") 'emus-playpause-status @@ -527,9 +642,9 @@ (kbd "P") 'emus-play-prev-album-status (kbd ",") 'emus-jump-10s-backward-status (kbd ".") 'emus-jump-10s-forward-status - (kbd "c") 'emus-centre-current-status)) + (kbd "c") 'emus-goto-current-status)) map) - "Keymap for emus.") + "Keymap for emus browser.") (define-derived-mode emus-browser-mode special-mode "emus-browser" "Major mode for EMUS music player file browser.")