Added commands to skip +/- 1 minute.
[emus.git] / emus.el
1 ;;; emus.el --- Simple mp3 player  -*- lexical-binding:t -*-
2
3 ;; Copyright (C) 2019 Tim Vaughan
4
5 ;; Author: Tim Vaughan <timv@ughan.xyz>
6 ;; Created: 8 December 2019
7 ;; Version: 1.0
8 ;; Keywords: multimedia
9 ;; Homepage: http://thelambdalab.xy/emus
10 ;; Package-Requires: ((emacs "26"))
11
12 ;; This file is not part of GNU Emacs.
13
14 ;; This program is free software: you can redistribute it and/or modify
15 ;; it under the terms of the GNU General Public License as published by
16 ;; the Free Software Foundation, either version 3 of the License, or
17 ;; (at your option) any later version.
18
19 ;; This program is distributed in the hope that it will be useful,
20 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
21 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
22 ;; GNU General Public License for more details.
23
24 ;; You should have received a copy of the GNU General Public License
25 ;; along with this file.  If not, see <http://www.gnu.org/licenses/>.
26
27 ;;; Commentary:
28
29 ;; This is a simple package for playing audio from a local directory
30 ;; tree of mp3 files.  It uses the program mpg123 as its back-end.
31 ;; Currently the library is loaded completely every time emus starts.
32
33 ;;; Code:
34
35 (provide 'emus)
36
37
38 ;;; Dependencies
39 ;;
40
41 (require 'seq)
42
43
44 ;;; Customizations
45 ;;
46
47 (defgroup emus nil
48   "Simple music player for Emacs."
49   :group 'multimedia)
50
51 (defcustom emus-directory "~/Music/"
52   "Directory containing audio files for emus."
53   :type '(string))
54
55 (defcustom emus-mpg123-program "mpg123"
56   "Name of (and, optionally, path to) mpg123 binary."
57   :type '(string))
58
59 (defface emus-artist
60   '((((background dark)) :inherit font-lock-string-face :background "#222" :extend t)
61     (t :inherit font-lock-string-face :background "#ddd" :extend t))
62   "Face used for artist names in browser.")
63
64 (defface emus-album
65   '((((background dark)) :inherit font-lock-constant-face :background "#111" :extend t)
66     (t :inherit font-lock-constant-face :background "#eee" :extend t))
67   "Face used for album names in browser.")
68
69 (defface emus-track
70   '((t :inherit font-lock-keyword-face))
71   "Face used for track titles in browser.")
72
73 (defface emus-track-current
74   '((t :inherit font-lock-keyword-face :inverse-video t :extend t))
75   "Face used for track titles in browser.")
76
77 (defface emus-cursor
78   '((t :inherit bold))
79   "Face used for current track cursor")
80
81
82 ;;; Global variables
83
84 (defvar emus--proc-in-use nil
85   "If non-nil, disables `emus-send-cmd'.
86 Used to prevent commands from interfering with library construction.")
87
88 (defvar emus-tracks nil
89   "Emus audio library.")
90
91 (defvar emus-current-track nil
92   "Currently-selected emus track.")
93
94 (defvar emus-state 'stopped
95   "Current playback state.")
96
97 (defvar emus-continuous-playback t
98   "If non-nil, emus will automatically play the next track when the current track is finished.")
99
100 (defvar emus-current-volume 100
101   "The current playback volume.")
102
103 (defvar emus-current-progress ""
104   "String describing the progress through the current track.")
105
106
107 ;;; mpg123 process
108 ;;
109
110
111 (defun emus-get-process ()
112   "Return current or new mpg123 process."
113   (let* ((emus-process-raw (get-process "emus-process"))
114          (emus-process (if emus-process-raw
115                            (if (process-live-p emus-process-raw)
116                                emus-process-raw
117                              (kill-process emus-process-raw)
118                              nil))))
119     (if emus-process
120         emus-process
121       (let ((proc
122              (make-process :name "emus-process"
123                            :command `(,emus-mpg123-program "-R"))))
124         (set-process-query-on-exit-flag proc nil)
125         ;; (process-send-string proc "silence\n")
126         proc))))
127
128 (defun emus--send-cmd-raw (cmd &rest args)
129   "Send a command CMD with args ARGS to the mpg123 process.
130 This procedure does not respect `emus--proc-in-use' and thus should only
131 be used by `emus--load-library'."
132     (process-send-string (emus-get-process)
133                          (concat
134                           (seq-reduce (lambda (s1 s2) (concat s1 " " s2)) args cmd)
135                           "\n")))
136
137 (defun emus-send-cmd (cmd &rest args)
138   "Send a command CMD with args ARGS to the mpg123 process."
139   (unless emus--proc-in-use
140     (apply #'emus--send-cmd-raw cmd args)))
141
142 (defun emus-kill-process ()
143   "Kill any existing mpg123 process."
144   (let ((emus-process (get-process "emus-process")))
145     (if emus-process
146         (kill-process emus-process))))
147
148 (defun emus-restart ()
149   "Restart emus, regardless of current state."
150   (interactive)
151   (emus-kill-process)
152   (setq emus-state 'stopped
153         emus--proc-in-use nil
154         emus-tracks nil))
155
156 ;;; Library
157 ;;
158
159 (defun emus-get-audio-files ()
160   "Get all mp3 files in main emus directory."
161   (mapcar
162    #'expand-file-name
163    (directory-files-recursively emus-directory ".*\\.mp3")))
164
165 (defun emus-get-playlist-files ()
166   "Get all m3u files in the main emus music directory."
167   (mapcar
168    #'expand-file-name
169    (directory-files-recursively emus-directory ".*\\.m3u")))
170
171 (defun emus-make-track (artist album title filename &optional pos)
172   "Create an object representing an emus track.
173 ARTIST, ALBUM and TITLE are used to describe the track, FILENAME
174 refers to the mp3 file containing the track.  If non-nil, POS
175 specifies the position of the record representing this track in the
176 emus browser buffer."
177   (list artist album title filename pos))
178
179 (defun emus-track-artist (track)
180   "The artist corresponding to TRACK."
181   (elt track 0))
182
183 (defun emus-track-album (track)
184   "The album corresponding to TRACK."
185   (elt track 1))
186
187 (defun emus-track-title (track)
188   "The title of TRACK."
189   (elt track 2))
190
191 (defun emus-track-file (track)
192   "The mp3 file corresponding to TRACK."
193   (elt track 3))
194
195 (defun emus-track-browser-pos (track)
196   "The location of the browser buffer record corresponding to TRACK."
197   (elt track 4))
198
199 (defun emus-set-track-browser-pos (track pos)
200   "Set the location of the browser buffer record corresponding to TRACK to POS."
201   (setf (seq-elt track 4) pos))
202
203 (defun emus--load-library (then)
204   "Initialize the emus track library.
205 Once the library is initialized, the function THEN is called."
206   (unless emus--proc-in-use
207     (setq emus--proc-in-use t)
208     (emus--suspend-cp)
209     (setq emus-state 'stopped)
210     (setq emus-tracks nil)
211     (let ((proc (emus-get-process))
212           (tagstr "")
213           (filenames (emus-get-audio-files)))
214       (set-process-filter proc (lambda (proc string)
215                                  (setq tagstr (concat tagstr string))
216                                  (when (string-suffix-p "@P 1\n" string)
217                                    (add-to-list 'emus-tracks
218                                                 (emus--make-track-from-tagstr (car filenames)
219                                                                               tagstr))
220                                    (setq tagstr "")
221                                    (setq filenames (cdr filenames))
222                                    (if filenames
223                                        (emus--send-cmd-raw "lp" (car filenames))
224                                      (set-process-filter proc nil)
225                                      (setq emus-tracks (reverse emus-tracks))
226                                      (emus--sort-tracks)
227                                      (emus--add-tracks-from-playlist-files)
228                                      (unless emus-current-track
229                                        (setq emus-current-track (car emus-tracks)))
230                                      (funcall then)
231                                      (emus--resume-cp)
232                                      (setq emus--proc-in-use nil)))))
233       (emus--send-cmd-raw "lp" (car filenames)))))
234
235 (defun emus--dump-tracks ()
236     emus-tracks)
237
238 (defun emus--make-track-from-tagstr (filename tagstr)
239   "Parse TAGSTR to populate the fields of a track corresponding to FILENAME."
240   (let ((artist "")
241         (album "")
242         (title ""))
243     (dolist (line (split-string tagstr "\n"))
244       (let ((found-artist (elt (split-string line "@I ID3v2.artist:") 1))
245             (found-album (elt (split-string line "@I ID3v2.album:") 1))
246             (found-title (elt (split-string line "@I ID3v2.title:") 1)))
247         (cond
248          (found-artist (setq artist found-artist))
249          (found-album (setq album found-album))
250          (found-title (setq title found-title)))))
251     (emus-make-track artist album title filename)))
252
253 (defun emus--add-tracks-from-playlist-files ()
254   (let ((tracks nil))
255     (dolist (filename (emus-get-playlist-files))
256       (let ((artist "Playlists")
257             (album (file-name-base filename))
258             (title nil)
259             (lines (split-string (with-temp-buffer
260                                    (insert-file-contents filename)
261                                    (buffer-string))
262                                  "\n")))
263         (dolist (line lines)
264           (pcase (string-trim line)
265             ((rx (: string-start
266                     (* space)
267                     "#extinf:"
268                     (* (not ",")) ","
269                     (let display-title (* any))
270                     string-end))
271              (setq title display-title))
272             ((rx (: string-start (* space) "#"))) ;skip other comments
273             ((rx (let filename (+ any)))
274              (setq tracks (cons (emus-make-track artist album (or title filename) filename)
275                                 tracks))
276              (setq title nil))))))
277     (setq emus-tracks (append emus-tracks (reverse tracks)))))
278
279 (defun emus--sort-tracks ()
280   "Sort the library tracks according to artist and album.
281 Leaves the track titles unsorted, so they will appear in the order specified
282 by the filesystem."
283   (setq emus-tracks
284         (sort emus-tracks
285               (lambda (r1 r2)
286                 (let ((artist1 (emus-track-artist r1))
287                       (artist2 (emus-track-artist r2)))
288                   (if (string= artist1 artist2)
289                       (let ((album1 (emus-track-album r1))
290                             (album2 (emus-track-album r2)))
291                         (string< album1 album2))
292                     (string< artist1 artist2)))))))
293
294 (defmacro emus--with-library (&rest body)
295   "Evaluate BODY with the library initialized."
296   `(if emus-tracks
297        (unless emus--proc-in-use ,@body)
298      (emus--load-library
299       (lambda () ,@body))))
300
301
302 ;;; Playback
303 ;;
304
305 (defun emus--suspend-cp ()
306   "Suspend continuous playback."
307   (setq emus-continuous-playback nil))
308
309 (defun emus--resume-cp ()
310   "Resume continuous playback."
311   (setq emus-continuous-playback t))
312
313 (defun emus--timestamp (seconds-total)
314   "Produce a timestamp string representation of SECONDS-TOTAL."
315   (let* ((seconds (truncate (mod seconds-total 60)))
316          (minutes (truncate (/ seconds-total 60))))
317     (format "%02d:%02d" minutes seconds)))
318
319 (defun emus-play-track (track)
320   "Set TRACK as current and start playing."
321   (emus--with-library
322    (let ((old-track emus-current-track))
323      (emus-send-cmd "l" (emus-track-file track))
324      (setq emus-state 'playing)
325      (setq emus-current-track track)
326      (setq emus-current-progress "")
327      (set-process-filter
328       (emus-get-process)
329       (lambda (_proc string)
330         (pcase string
331           ((and "@P 0\n"
332                 (guard emus-continuous-playback)
333                 (guard (eq emus-state 'playing)))
334            (emus-play-next))
335           ((rx (: string-start
336                   "@F "
337                   (+ digit)
338                   " "
339                   (+ digit)
340                   " "
341                   (let left-str (+ (not " ")))
342                   " "
343                   (let right-str (+ any))))
344            (let* ((left (string-to-number left-str))
345                   (right (string-to-number right-str))
346                   (total (+ left right)))
347              (setq emus-current-progress
348                    (format " %s/%s"
349                            (emus--timestamp left)
350                            (emus--timestamp total))))))))
351      (emus--update-track old-track)
352      (emus--update-track track)
353      (emus--resume-cp)
354      (emus-goto-current))))
355
356 (defun emus-select-track (track)
357   "Set TRACK as current, but do not start playing."
358   (emus--with-library
359    (let ((old-track emus-current-track))
360      (setq emus-state 'stopped)
361      (setq emus-current-track track)
362      (emus--update-track old-track)
363      (emus--update-track track)
364      (emus-send-cmd "o")
365      (emus--resume-cp)
366      (emus-goto-current))))
367
368 (defun emus-stop ()
369   "Stop playback of the current track."
370   (interactive)
371   (emus--with-library
372    (setq emus-state 'stopped)
373    (emus--update-track emus-current-track)
374    (emus-send-cmd "s")))
375
376 (defun emus-playpause ()
377   "Begin playback of the current track.
378 If the track is already playing, pause playback.
379 If the track is currently paused, resume playback."
380   (interactive)
381   (emus--with-library
382    (when emus-current-track
383      (if (eq emus-state 'stopped)
384          (emus-play-track emus-current-track)
385        (emus-send-cmd "p")
386        (pcase emus-state
387          ((or 'paused 'stopped) (setq emus-state 'playing))
388          ('playing (setq emus-state 'paused)))
389        (unless (eq emus-state 'paused)))
390      (emus--update-track emus-current-track))))
391
392 (defun emus-set-volume (pct)
393   "Set the playback volume to PCT %."
394   (emus--with-library
395    (setq emus-current-volume pct)
396    (emus-send-cmd "v" (number-to-string pct))))
397
398 (defun emus-volume-increase-by (delta)
399   "Increase the playback volume by DELTA %."
400   (emus-set-volume (max 0 (min 100 (+ emus-current-volume delta)))))
401
402 (defun emus-volume-up ()
403   "Increase the playback volume by 10%."
404   (interactive)
405   (emus-volume-increase-by 10))
406
407 (defun emus-volume-down ()
408   "Decrease the playback volume by 10%."
409   (interactive)
410   (emus-volume-increase-by -10))
411
412 (defun emus--play-adjacent-track (&optional prev)
413   "Play the next track in the library, or the previous if PREV is non-nil."
414   (emus--with-library
415    (let ((idx (seq-position emus-tracks emus-current-track))
416          (offset (if prev -1 +1)))
417      (if idx
418          (let ((next-track (elt emus-tracks (+ idx offset))))
419            (if next-track
420                (if (eq emus-state 'playing)
421                    (emus-play-track next-track)
422                  (emus-select-track next-track))
423              (error "Track does not exist")))
424        (error "No track selected")))))
425
426 (defun emus--play-adjacent-album (&optional prev)
427   "Play the first track of the next album in the library.
428 If PREV is non-nil, plays the last track of the previous album."
429   (emus--with-library
430    (let ((idx (seq-position emus-tracks emus-current-track)))
431      (if idx
432          (let* ((search-list (if prev
433                                  (reverse (seq-subseq emus-tracks 0 idx))
434                                (seq-subseq emus-tracks (+ idx 1))))
435                 (current-album (emus-track-album emus-current-track))
436                 (next-track (seq-some (lambda (r)
437                                         (if (string= (emus-track-album r)
438                                                      current-album)
439                                             nil
440                                           r))
441                                       search-list)))
442            (if next-track
443                (if (eq emus-state 'playing)
444                    (emus-play-track next-track)
445                  (emus-select-track next-track))
446              (error "Track does not exist")))
447        (error "No track selected")))))
448
449 (defun emus-play-next ()
450   "Play the next track in the library."
451   (interactive)
452   (emus--play-adjacent-track))
453
454 (defun emus-play-prev ()
455   "Play the previous track in the library."
456   (interactive)
457   (emus--play-adjacent-track t))
458
459 (defun emus-play-next-album ()
460   "Play the first track of the next album in the library."
461   (interactive)
462   (emus--play-adjacent-album))
463
464 (defun emus-play-prev-album ()
465   "Play the last track of the previous album in the library."
466   (interactive)
467   (emus--play-adjacent-album t))
468
469 (defun emus-jump (seconds)
470   "Jump forward in current track by SECONDS seconds."
471   (emus--with-library
472    (emus-send-cmd "jump" (format "%+ds" seconds))))
473
474 (defun emus-jump-10s-forward ()
475   "Jump 10 seconds forward in current track."
476   (interactive)
477   (emus-jump 10))
478
479 (defun emus-jump-10s-backward ()
480   "Jump 10 seconds backward in current track."
481   (interactive)
482   (emus-jump -10))
483
484 (defun emus-jump-1m-forward ()
485   "Jump 1 minute forward in current track."
486   (interactive)
487   (emus-jump 60))
488
489 (defun emus-jump-1m-backward ()
490   "Jump 1 minute backward in current track."
491   (interactive)
492   (emus-jump -60))
493
494 (defun emus-display-status ()
495   "Display the current playback status in the minibuffer."
496   (interactive)
497   (emus--with-library
498    (message
499     (concat "Emus: Volume %d%%"
500             (pcase emus-state
501               ('stopped " [Stopped]")
502               ('paused (format " [Paused%s]" emus-current-progress))
503               ('playing (format " [Playing%s]" emus-current-progress))
504               (_ ""))
505             " %s")
506     emus-current-volume
507     (if emus-current-track
508         (format "- %.30s (%.20s, %.20s)"
509                 (emus-track-title emus-current-track)
510                 (emus-track-album emus-current-track)
511                 (emus-track-artist emus-current-track))
512       ""))))
513
514
515 ;;; Browser
516 ;;
517
518 (defun emus--insert-track (track &optional prev-track first)
519   "Insert a button representing TRACK into the current buffer.
520
521 When provided, PREV-TRACK is used to determine whether to insert additional
522 headers representing the artist or the album title.
523
524 If non-nil, FIRST indicates that the track is the first in the library
525 and thus requires both artist and album headers."
526   (let* ((artist (emus-track-artist track))
527          (album (emus-track-album track))
528          (title (emus-track-title track))
529          (album-symb (intern (concat artist album)))
530          (help-str (format "mouse-1, RET: Play '%.30s' (%.20s)" title artist))
531          (field (intern album))) ;Allows easy jumping between albums with cursor.
532     (when (or prev-track first)
533       (unless (equal (emus-track-artist prev-track) artist)
534         (insert-text-button
535          (propertize artist 'face 'emus-artist)
536          'action #'emus--click-track
537          'follow-link t
538          'help-echo help-str
539          'emus-track track
540          'field field)
541         (insert (propertize "\n"
542                             'face 'emus-artist
543                             'field field)))
544       (unless (equal (emus-track-album prev-track) album)
545         (insert-text-button
546          (propertize (concat "  " album) 'face 'emus-album)
547          'action #'emus--click-track
548          'follow-link t
549          'help-echo help-str
550          'emus-track track
551          'field field)
552         (insert (propertize "\n"
553                             'face 'emus-album
554                             'field field))))
555     (emus-set-track-browser-pos track (point))
556     (let ((is-current (equal track emus-current-track)))
557       (insert-text-button
558        (concat
559         (if is-current
560             (propertize
561              (pcase emus-state
562                ('playing "->")
563                ('paused "-)")
564                ('stopped "-]"))
565              'face 'emus-cursor)
566           (propertize "  " 'face 'default))
567         (propertize (format "   %s" title)
568                     'face (if is-current
569                               'emus-track-current
570                             'emus-track)))
571        'action #'emus--click-track
572        'follow-link t
573        'help-echo help-str
574        'emus-track track
575        'invisible album-symb
576        'field field)
577       (insert (propertize "\n"
578                           'face (if is-current
579                                     'emus-track-current
580                                   'emus-track)
581                           'field field
582                           'invisible album-symb)))))
583
584 (defun emus--update-track (track)
585   "Rerender entry for TRACK in emus browser buffer.
586 Used to update browser display when `emus-current-track' and/or `emus-state' changes."
587   (let ((track-pos (emus-track-browser-pos track)))
588     (when (and (get-buffer "*emus*")
589                (emus-track-browser-pos track))
590       (with-current-buffer "*emus*"
591         (let ((inhibit-read-only t)
592               (old-point (point)))
593           (goto-char track-pos)
594           (search-forward "\n")
595           (delete-region track-pos (point))
596           (goto-char track-pos)
597           (emus--insert-track track)
598           (goto-char old-point))))))
599
600 (defun emus--render-tracks ()
601   "Render all library tracks in emus browser buffer."
602   (with-current-buffer "*emus*"
603     (let ((inhibit-read-only t)
604           (old-pos (point)))
605       (erase-buffer)
606       (goto-char (point-min))
607       (let ((prev-track nil))
608         (dolist (track emus-tracks)
609           (emus--insert-track track prev-track (not prev-track))
610           (setq prev-track track)))
611       (goto-char old-pos))))
612
613 (defun emus--click-track (button)
614   "Begin playback of track indicated by BUTTON."
615   (emus-play-track (button-get button 'emus-track))
616   (emus-display-status))
617
618 (defun emus-goto-current ()
619   "Move point to the current track in the browser buffer, if available."
620   (interactive)
621   (when (and (get-buffer "*emus*")
622              emus-current-track)
623     (with-current-buffer "*emus*"
624         (goto-char (emus-track-browser-pos emus-current-track)))))
625
626 (defun emus-browse ()
627   "Switch to *emus* audio library browser."
628   (interactive)
629   (emus--with-library
630    (pop-to-buffer-same-window "*emus*")
631    (emus-browser-mode)
632    (emus--render-tracks)
633    (emus-goto-current)))
634
635 (defun emus-refresh ()
636   "Refresh the emus library."
637   (interactive)
638   (emus-stop)
639   (setq emus-tracks nil)
640   (emus-browse))
641
642
643 ;;; Playback + status display commands
644 ;;
645
646 (defun emus-playpause-status ()
647   "Start, pause or resume playback, then display the emus status in the minibuffer."
648   (interactive)
649   (emus-playpause)
650   (emus-display-status))
651
652 (defun emus-stop-status ()
653   "Stop playback, then display the emus status in the minibuffer."
654   (interactive)
655   (emus-stop)
656   (emus-display-status))
657
658 (defun emus-volume-up-status ()
659   "Increase volume by 10%, then display the emus status in the minibuffer."
660   (interactive)
661   (emus-volume-up)
662   (emus-display-status))
663
664 (defun emus-volume-down-status ()
665   "Decrease volume by 10%, then display the emus status in the minibuffer."
666   (interactive)
667   (emus-volume-down)
668   (emus-display-status))
669
670 (defun emus-play-next-status ()
671   "Play next track, then display the emus status in the minibuffer."
672   (interactive)
673   (emus-play-next)
674   (emus-display-status))
675
676 (defun emus-play-prev-status ()
677   "Play previous track, then display the emus status in the minibuffer."
678   (interactive)
679   (emus-play-prev)
680   (emus-display-status))
681
682 (defun emus-play-next-album-status ()
683   "Play first track of next album, then display the emus status in the minibuffer."
684   (interactive)
685   (emus-play-next-album)
686   (emus-display-status))
687
688 (defun emus-play-prev-album-status ()
689   "Play last track of previous album, then display the emus status in the minibuffer."
690   (interactive)
691   (emus-play-prev-album)
692   (emus-display-status))
693
694 (defun emus-jump-10s-forward-status ()
695   "Jump 10s forward in current track, then display the emus status in the minibuffer."
696   (interactive)
697   (emus-jump-10s-forward)
698   (emus-display-status))
699
700 (defun emus-jump-10s-backward-status ()
701   "Jump 10s backward in current track, then display the emus status in the minibuffer."
702   (interactive)
703   (emus-jump-10s-backward)
704   (emus-display-status))
705
706 (defun emus-jump-1m-forward-status ()
707   "Jump 10s forward in current track, then display the emus status in the minibuffer."
708   (interactive)
709   (emus-jump-1m-forward)
710   (emus-display-status))
711
712 (defun emus-jump-1m-backward-status ()
713   "Jump 10s backward in current track, then display the emus status in the minibuffer."
714   (interactive)
715   (emus-jump-1m-backward)
716   (emus-display-status))
717
718 (defun emus-goto-current-status ()
719   "Move point to the current track, then display the emus status in the minibuffer."
720   (interactive)
721   (emus-goto-current)
722   (emus-display-status))
723
724 (defun emus-refresh-status ()
725   "Refresh the emus library, then display the emus status in the minibuffer."
726   (interactive)
727   (emus-stop)
728   (setq emus-tracks nil)
729   (emus--with-library
730    (emus-browse)
731    (emus-display-status)))
732
733 (defvar emus-browser-mode-map
734   (let ((map (make-sparse-keymap)))
735     (define-key map (kbd "SPC") 'emus-playpause-status)
736     (define-key map (kbd "o") 'emus-stop-status)
737     (define-key map (kbd "+") 'emus-volume-up-status)
738     (define-key map (kbd "=") 'emus-volume-up-status)
739     (define-key map (kbd "-") 'emus-volume-down-status)
740     (define-key map (kbd "R") 'emus-refresh-status)
741     (define-key map (kbd "n") 'emus-play-next-status)
742     (define-key map (kbd "p") 'emus-play-prev-status)
743     (define-key map (kbd "N") 'emus-play-next-album-status)
744     (define-key map (kbd "P") 'emus-play-prev-album-status)
745     (define-key map (kbd ",") 'emus-jump-10s-backward-status)
746     (define-key map (kbd ".") 'emus-jump-10s-forward-status)
747     (define-key map (kbd "<") 'emus-jump-1m-backward-status)
748     (define-key map (kbd ">") 'emus-jump-1m-forward-status)
749     (define-key map (kbd "c") 'emus-goto-current-status)
750     (when (fboundp 'evil-define-key*)
751       (evil-define-key* 'motion map
752                         (kbd "SPC") 'emus-playpause-status
753                         (kbd "o") 'emus-stop-status
754                         (kbd "+") 'emus-volume-up-status
755                         (kbd "=") 'emus-volume-up-status
756                         (kbd "-") 'emus-volume-down-status
757                         (kbd "R") 'emus-refresh-status
758                         (kbd "n") 'emus-play-next-status
759                         (kbd "p") 'emus-play-prev-status
760                         (kbd "N") 'emus-play-next-album-status
761                         (kbd "P") 'emus-play-prev-album-status
762                         (kbd ",") 'emus-jump-10s-backward-status
763                         (kbd ".") 'emus-jump-10s-forward-status
764                         (kbd "<") 'emus-jump-1m-backward-status
765                         (kbd ">") 'emus-jump-1m-forward-status
766                         (kbd "c") 'emus-goto-current-status))
767     map)
768   "Keymap for emus browser.")
769
770 (defun emus-restart ()
771   "Restart emus."
772   (interactive)
773   (emus-kill-process))
774
775 (define-derived-mode emus-browser-mode special-mode "emus-browser"
776   "Major mode for EMUS music player file browser."
777   (setq-local buffer-invisibility-spec nil))
778
779 (when (fboundp 'evil-set-initial-state)
780   (evil-set-initial-state 'emus-browser-mode 'motion))
781
782 ;;; emus.el ends here