It's actually playing music.
[emus.git] / emus.el
1 ;;; emus.el --- Simple music player for Emacs.  -*- lexical-binding:t -*-
2
3 ;; Author: Tim Vaughan <timv@ughan.xyz>
4 ;; Version: 1.0
5 ;; Keywords: multimedia
6 ;; URL: https://thelambdalab.xyz/emus
7
8 ;;; Commentary:
9
10 ;; This is a simple package for playing audio from a local library
11 ;; of audio files.
12
13 ;;; Code:
14
15 ;;; Customizations
16 ;;
17
18 (defgroup emus nil
19   "Simple music player for Emacs."
20   :group 'multimedia)
21
22 (defcustom emus-directory "~/Music/"
23   "Directory containing audio files for emus."
24   :type '(string))
25
26 (defcustom emus-mpg123-program "mpg123"
27   "Name of (and, optionally, path to) mpg123 binary."
28   :type '(string))
29
30 ;;; Library
31 ;;
32
33 (defvar emus-library nil
34   "Emus audio library.")
35
36 (defun emus-get-audio-files ()
37   "Get all mp3 files in main emus directory."
38   (directory-files-recursively emus-directory ".*\\.mp3"))
39
40 (defvar emus-records nil)
41
42 (defun emus-make-record (filename tagstr)
43   (let ((artist "")
44         (album "")
45         (title ""))
46     (dolist (line (split-string tagstr "\n"))
47       (let ((found-artist (elt (split-string line "@I ID3v2.artist:") 1))
48             (found-album (elt (split-string line "@I ID3v2.album:") 1))
49             (found-title (elt (split-string line "@I ID3v2.title:") 1)))
50         (cond
51          (found-artist (setq artist found-artist))
52          (found-album (setq album found-album))
53          (found-title (setq title found-title)))))
54     (list artist album title filename)))
55
56 (defun emus-record-artist (record)
57   (elt record 0))
58
59 (defun emus-record-album (record)
60   (elt record 1))
61
62 (defun emus-record-title (record)
63   (elt record 2))
64
65 (defun emus-record-file (record)
66   (elt record 3))
67
68 (defun emus-update-records (then)
69   (let ((proc (emus-get-process))
70         (tagstr "")
71         (filenames (emus-get-audio-files)))
72     (setq emus-records nil)
73     (set-process-filter proc (lambda (proc string)
74                                (setq tagstr (concat tagstr string))
75                                (when (string-suffix-p "@P 1\n" string)
76                                  (add-to-list 'emus-records
77                                               (emus-make-record (car filenames)
78                                                                 tagstr))
79                                  (setq tagstr "")
80                                  (setq filenames (cdr filenames))
81                                  (if filenames
82                                      (emus-send-cmd "lp" (car filenames))
83                                    (set-process-filter proc nil)
84                                    (funcall then)))))
85     (emus-send-cmd "lp" (car filenames))))
86
87 ;;; mpg123 process
88 ;;
89
90 (defvar emus-proc-in-use nil)
91
92 (defun emus-get-process ()
93   "Return current or new mpg123 process."
94   (let* ((emus-process-raw (get-process "emus-process"))
95          (emus-process (if emus-process-raw
96                            (if (process-live-p emus-process-raw)
97                                emus-process-raw
98                              (kill-process emus-process-raw)
99                              nil))))
100     (if emus-process
101         emus-process
102       (make-process :name "emus-process"
103                     ;; :buffer (get-buffer-create "*emus-process*")
104                     :command `(,emus-mpg123-program "-R")))))
105
106 (defun emus-send-cmd (cmd &rest args)
107   (process-send-string (emus-get-process)
108                        (concat
109                         (seq-reduce (lambda (s1 s2) (concat s1 " " s2)) args cmd)
110                         "\n")))
111
112 ;;; Playback
113 ;;
114
115 (defvar emus-currently-playing nil)
116
117 (defun emus-play-record (record)
118   (setq emus-currently-playing record)
119   (emus-send-cmd "l" (emus-record-file record)))
120
121 (defun emus-stop ()
122   (interactive)
123   (emus-send-cmd "s")
124   (setq emus-currently-playing nil))
125
126 (defun emus-playpause ()
127   (interactive)
128   (emus-send-cmd "p"))
129
130
131 (defun emus-volume (pct)
132   (emus-send-cmd "v" (number-to-string pct)))
133
134 (defvar emus-current-volume 10)
135
136 (defun emus-volume-delta (delta)
137   (setq emus-current-volume (max 0 (min 100 (+ emus-current-volume delta))))
138   (emus-volume emus-current-volume))
139
140 (defun emus-volume-up ()
141   (interactive)
142   (emus-volume-delta 10))
143
144 (defun emus-volume-down ()
145   (interactive)
146   (emus-volume-delta -10))
147
148
149 ;;; Browser
150 ;;
151
152 (defun emus-render-record (record)
153    (insert-text-button
154     (concat
155      (propertize (format "%-20.20s" (emus-record-artist record))
156                  'face 'font-lock-keyword-face)
157      (propertize (format "%  -20.20s" (emus-record-album record))
158                  'face 'font-lock-function-name-face)
159      (propertize (format "  %s" (emus-record-title record))
160                  'face 'font-lock-string-face))
161     'action #'emus-click-record
162     'follow-link t
163     'emus-record record)
164   (insert "\n"))
165
166 (defun emus-render-records ()
167   (with-current-buffer "*emus*"
168     (let ((inhibit-read-only t))
169       (save-excursion
170         (erase-buffer)
171         (goto-char (point-min))
172         (dolist (record emus-records)
173           (emus-render-record record))))))
174
175 (defun emus-click-record (button)
176   (emus-play-record (button-get button 'emus-record)))
177
178 (defun emus-browse ()
179   "Switch to *emus* audio library browser."
180   (interactive)
181   (switch-to-buffer "*emus*")
182   (emus-mode)
183   (emus-volume emus-current-volume)
184   (if emus-records
185       (emus-render-records)
186     (emus-update-records #'emus-render-records)))
187
188 (defvar emus-mode-map
189   (let ((map (make-sparse-keymap)))
190     (define-key map (kbd "SPC") 'emus-playpause)
191     (define-key map (kbd "o") 'emus-stop)
192     (define-key map (kbd "+") 'emus-volume-up)
193     (define-key map (kbd "=") 'emus-volume-up)
194     (define-key map (kbd "-") 'emus-volume-down)
195     (when (fboundp 'evil-define-key*)
196       (evil-define-key* 'motion map
197         (kbd "SPC") 'emus-playpause
198         (kbd "o") 'emus-stop
199         (kbd "+") 'emus-volume-up
200         (kbd "=") 'emus-volume-up
201         (kbd "-") 'emus-volume-down))
202     map)
203   "Keymap for emus.")
204
205 (define-derived-mode emus-mode special-mode "Emus"
206   "Major mode for EMUS music player.")
207
208 (when (fboundp 'evil-set-initial-state)
209   (evil-set-initial-state 'emus-mode 'motion))
210
211 ;;; Debugging
212
213 ;;; emus.el ends here