-;;; emus.el --- Simple music player for Emacs
+;;; emus.el --- Simple music player for Emacs. -*- lexical-binding:t -*-
-;; Author: T. G. Vaughan <tgvaughan@gmail.com>
+;; Author: Tim Vaughan <timv@ughan.xyz>
;; Version: 1.0
;; Keywords: multimedia
-;; URL: http://github.com/tgvaughan/emus
+;; URL: https://thelambdalab.xyz/emus
;;; Commentary:
;;; Code:
+;;; Customizations
+;;
+
(defgroup emus nil
- "Simple music player for Emacs inspired by CMUS."
+ "Simple music player for Emacs."
:group 'multimedia)
(defcustom emus-directory "~/Music/"
"Directory containing audio files for emus."
- :type 'string
- :group 'emus)
+ :type '(string))
-(defcustom emus-mpg123-excecutable "mpg123"
+(defcustom emus-mpg123-program "mpg123"
"Name of (and, optionally, path to) mpg123 binary."
- :type 'string
- :group 'emus)
+ :type '(string))
+
+;;; 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"))
-(defun emus ()
- "Switch to *emus* audio library buffer."
+(defvar emus-records nil)
+
+(defun emus-make-record (filename tagstr)
+ (let ((artist "")
+ (album "")
+ (title ""))
+ (dolist (line (split-string tagstr "\n"))
+ (let ((found-artist (elt (split-string line "@I ID3v2.artist:") 1))
+ (found-album (elt (split-string line "@I ID3v2.album:") 1))
+ (found-title (elt (split-string line "@I ID3v2.title:") 1)))
+ (cond
+ (found-artist (setq artist found-artist))
+ (found-album (setq album found-album))
+ (found-title (setq title found-title)))))
+ (list artist album title filename)))
+
+(defun emus-record-artist (record)
+ (elt record 0))
+
+(defun emus-record-album (record)
+ (elt record 1))
+
+(defun emus-record-title (record)
+ (elt record 2))
+
+(defun emus-record-file (record)
+ (elt record 3))
+
+(defun emus-update-records (then)
+ (let ((proc (emus-get-process))
+ (tagstr "")
+ (filenames (emus-get-audio-files)))
+ (setq emus-records 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-records
+ (emus-make-record (car filenames)
+ tagstr))
+ (setq tagstr "")
+ (setq filenames (cdr filenames))
+ (if filenames
+ (emus-send-cmd "lp" (car filenames))
+ (set-process-filter proc nil)
+ (funcall then)))))
+ (emus-send-cmd "lp" (car filenames))))
+
+;;; 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
+ (make-process :name "emus-process"
+ ;; :buffer (get-buffer-create "*emus-process*")
+ :command `(,emus-mpg123-program "-R")))))
+
+(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")))
+
+;;; Playback
+;;
+
+(defvar emus-currently-playing nil)
+
+(defun emus-play-record (record)
+ (setq emus-currently-playing record)
+ (emus-send-cmd "l" (emus-record-file record)))
+
+(defun emus-stop ()
+ (interactive)
+ (emus-send-cmd "s")
+ (setq emus-currently-playing nil))
+
+(defun emus-playpause ()
+ (interactive)
+ (emus-send-cmd "p"))
+
+
+(defun emus-volume (pct)
+ (emus-send-cmd "v" (number-to-string pct)))
+
+(defvar emus-current-volume 10)
+
+(defun emus-volume-delta (delta)
+ (setq emus-current-volume (max 0 (min 100 (+ emus-current-volume delta))))
+ (emus-volume emus-current-volume))
+
+(defun emus-volume-up ()
+ (interactive)
+ (emus-volume-delta 10))
+
+(defun emus-volume-down ()
+ (interactive)
+ (emus-volume-delta -10))
+
+
+;;; Browser
+;;
+
+(defun emus-render-record (record)
+ (insert-text-button
+ (concat
+ (propertize (format "%-20.20s" (emus-record-artist record))
+ 'face 'font-lock-keyword-face)
+ (propertize (format "% -20.20s" (emus-record-album record))
+ 'face 'font-lock-function-name-face)
+ (propertize (format " %s" (emus-record-title record))
+ 'face 'font-lock-string-face))
+ 'action #'emus-click-record
+ 'follow-link t
+ 'emus-record record)
+ (insert "\n"))
+
+(defun emus-render-records ()
+ (with-current-buffer "*emus*"
+ (let ((inhibit-read-only t))
+ (save-excursion
+ (erase-buffer)
+ (goto-char (point-min))
+ (dolist (record emus-records)
+ (emus-render-record record))))))
+
+(defun emus-click-record (button)
+ (emus-play-record (button-get button 'emus-record)))
+
+(defun emus-browse ()
+ "Switch to *emus* audio library browser."
(interactive)
(switch-to-buffer "*emus*")
- (emus-mode))
+ (emus-mode)
+ (emus-volume emus-current-volume)
+ (if emus-records
+ (emus-render-records)
+ (emus-update-records #'emus-render-records)))
+
+(defvar emus-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)
+ (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))
+ map)
+ "Keymap for emus.")
(define-derived-mode emus-mode special-mode "Emus"
- "Major mode for EMUS music player."
+ "Major mode for EMUS music player.")
- (setq-local default-directory emus-directory)
+(when (fboundp 'evil-set-initial-state)
+ (evil-set-initial-state 'emus-mode 'motion))
- (let ((player (make-process :name "mpg123" :command '(emus-mpg123-excecutable "-R"))))
- (process-send-string player "load The Midnight - Endless Summer - 01 Endless Summer.mp3\n")))
+;;; Debugging
;;; emus.el ends here