Blame


1 9df6ca91 2020-09-20 op Without a specific motivation, I wanted to try some Emacs music
2 9df6ca91 2020-09-20 op players. I briefly tried BONGO, to finally set on EMMS, the Emacs
3 9df6ca91 2020-09-20 op Multi Media System.
4 9df6ca91 2020-09-20 op
5 9df6ca91 2020-09-20 op One thing that I really appreciate about EMMS is how minimal it is:
6 9df6ca91 2020-09-20 op there is only one interface, the playlist buffer, and nothing else.
7 9df6ca91 2020-09-20 op
8 9df6ca91 2020-09-20 op As Emacs itself, EMMS is also very flexible, and thanks to this I
9 9df6ca91 2020-09-20 op believe I've found/build the best music player ever with it. If you
10 9df6ca91 2020-09-20 op use Emacs I really encourage you to try EMMS out. The rest of this
11 9df6ca91 2020-09-20 op post is just bits of my configuration and some screenshots.
12 9df6ca91 2020-09-20 op
13 9df6ca91 2020-09-20 op ## Setup
14 9df6ca91 2020-09-20 op
15 9df6ca91 2020-09-20 op I'm not doing some fancy stuff to set it up. I'm just doing what
16 9df6ca91 2020-09-20 op suggested in the documentation:
17 9df6ca91 2020-09-20 op
18 9df6ca91 2020-09-20 op ```elisp
19 9df6ca91 2020-09-20 op (require 'emms-setup)
20 9df6ca91 2020-09-20 op (emms-all)
21 9df6ca91 2020-09-20 op (emms-default-players)
22 9df6ca91 2020-09-20 op
23 9df6ca91 2020-09-20 op ;; fancy
24 9df6ca91 2020-09-20 op (setq emms-mode-line-format "「%s」")
25 9df6ca91 2020-09-20 op ```
26 9df6ca91 2020-09-20 op
27 9df6ca91 2020-09-20 op ## Select a song
28 9df6ca91 2020-09-20 op
29 9df6ca91 2020-09-20 op While you can easily select a song off a playlist in the `*EMMS
30 9df6ca91 2020-09-20 op Playlist*` buffer, I prefer to use selectrum to do it. The following
31 9df6ca91 2020-09-20 op code is what I ended up with.
32 9df6ca91 2020-09-20 op
33 9df6ca91 2020-09-20 op ```elisp
34 9df6ca91 2020-09-20 op (defun my/selectrum-emms ()
35 9df6ca91 2020-09-20 op "Select and play a song from the current EMMS playlist."
36 9df6ca91 2020-09-20 op (interactive)
37 9df6ca91 2020-09-20 op (with-current-emms-playlist
38 9df6ca91 2020-09-20 op (emms-playlist-mode-center-current)
39 9df6ca91 2020-09-20 op (let* ((selectrum-should-sort-p nil)
40 9df6ca91 2020-09-20 op (current-line-number (line-number-at-pos (point)))
41 9df6ca91 2020-09-20 op (lines (cl-loop
42 9df6ca91 2020-09-20 op with min-line-number = (line-number-at-pos (point-min))
43 9df6ca91 2020-09-20 op with buffer-text-lines = (split-string (buffer-string) "\n")
44 9df6ca91 2020-09-20 op with lines = nil
45 9df6ca91 2020-09-20 op for l in buffer-text-lines
46 9df6ca91 2020-09-20 op for n = min-line-number then (1+ n)
47 9df6ca91 2020-09-20 op do (push (propertize l' line-num n)
48 9df6ca91 2020-09-20 op lines)
49 9df6ca91 2020-09-20 op finally return (nreverse lines)))
50 9df6ca91 2020-09-20 op (selected-line (selectrum-read "Song: " lines
51 9df6ca91 2020-09-20 op :default-candidate (nth (1- current-line-number) lines)
52 9df6ca91 2020-09-20 op :require-match t
53 9df6ca91 2020-09-20 op :no-move-default-candidate t)))
54 9df6ca91 2020-09-20 op (when selected-line
55 9df6ca91 2020-09-20 op (let ((line (get-text-property 0 'line-num selected-line)))
56 9df6ca91 2020-09-20 op (goto-line line)
57 9df6ca91 2020-09-20 op (emms-playlist-mode-play-smart)
58 9df6ca91 2020-09-20 op (emms-playlist-mode-center-current))))))
59 9df6ca91 2020-09-20 op ```
60 9df6ca91 2020-09-20 op
61 a3ab6f61 2020-09-22 op ![emms selectrum](/img/emms-selectrum.png "Selecting a song from the current playlist with selectrum")
62 9df6ca91 2020-09-20 op
63 9df6ca91 2020-09-20 op Should be pretty easy to adapt it to use the standard
64 9df6ca91 2020-09-20 op `completing-read`.
65 9df6ca91 2020-09-20 op
66 9df6ca91 2020-09-20 op ## Hydra
67 9df6ca91 2020-09-20 op
68 ad77750f 2020-09-24 op I usually listen to music while doing various things, and I don't like
69 ad77750f 2020-09-24 op the context switch from/to what I'm working on and the playlist
70 ad77750f 2020-09-24 op buffer.
71 9df6ca91 2020-09-20 op
72 9df6ca91 2020-09-20 op An hydra seems the best approach: you get "bindings that stick around"
73 9df6ca91 2020-09-20 op with just the little UI you need to manage your player. It's
74 9df6ca91 2020-09-20 op basically a mini-popup-ui for music controls. (in addition, I've
75 9df6ca91 2020-09-20 op recently discovered the hydra package, and I couldn't find an excuse
76 9df6ca91 2020-09-20 op not to build a hydra for EMMS).
77 9df6ca91 2020-09-20 op
78 a3ab6f61 2020-09-22 op ![emms hydra](/img/emms-hydra.png "An Hydra for EMMS")
79 9df6ca91 2020-09-20 op
80 9df6ca91 2020-09-20 op I'm still pretty new to hydra, and I'm not 100% happy about how I'm
81 9df6ca91 2020-09-20 op doing the interpolation, but anyway, here's the code.
82 9df6ca91 2020-09-20 op
83 9df6ca91 2020-09-20 op First, some helper functions
84 9df6ca91 2020-09-20 op
85 9df6ca91 2020-09-20 op ```elisp
86 9df6ca91 2020-09-20 op (defun my/active-p (x)
87 9df6ca91 2020-09-20 op "Return a string representation for the (assumed boolean) X."
88 9df6ca91 2020-09-20 op (if x 'yup 'nop))
89 9df6ca91 2020-09-20 op
90 9df6ca91 2020-09-20 op (defun my/emms-player-status ()
91 9df6ca91 2020-09-20 op "Return the state of the EMMS player: `not-active', `playing', `paused' or `dunno'.
92 9df6ca91 2020-09-20 op
93 9df6ca91 2020-09-20 op Modeled after `emms-player-pause'."
94 9df6ca91 2020-09-20 op (cond ((not emms-player-playing-p)
95 9df6ca91 2020-09-20 op ;; here we should return 'not-active. The fact is that
96 9df6ca91 2020-09-20 op ;; when i change song, there is a short amount of time
97 9df6ca91 2020-09-20 op ;; where we are ``not active'', and the hydra is rendered
98 9df6ca91 2020-09-20 op ;; always during that short amount of time. So we cheat a
99 9df6ca91 2020-09-20 op ;; little.
100 9df6ca91 2020-09-20 op 'playing)
101 9df6ca91 2020-09-20 op
102 9df6ca91 2020-09-20 op (emms-player-paused-p
103 9df6ca91 2020-09-20 op (let ((resume (emms-player-get emms-player-playing-p 'resume))
104 9df6ca91 2020-09-20 op (pause (emms-player-get emms-player-playing-p 'pause)))
105 9df6ca91 2020-09-20 op (cond (resume 'paused)
106 9df6ca91 2020-09-20 op (pause 'playing)
107 9df6ca91 2020-09-20 op (t 'dunno))))
108 9df6ca91 2020-09-20 op (t (let ((pause (emms-player-get emms-player-playing-p 'pause)))
109 9df6ca91 2020-09-20 op (if pause 'playing 'dunno)))))
110 9df6ca91 2020-09-20 op
111 9df6ca91 2020-09-20 op (defun my/emms-toggle-time-display ()
112 9df6ca91 2020-09-20 op "Toggle the display of time information in the modeline"
113 9df6ca91 2020-09-20 op (interactive "")
114 9df6ca91 2020-09-20 op (if emms-playing-time-display-p
115 9df6ca91 2020-09-20 op (emms-playing-time-disable-display)
116 9df6ca91 2020-09-20 op (emms-playing-time-enable-display)))
117 9df6ca91 2020-09-20 op ```
118 9df6ca91 2020-09-20 op
119 9df6ca91 2020-09-20 op and then the full hydra
120 9df6ca91 2020-09-20 op
121 9df6ca91 2020-09-20 op ```elisp
122 9df6ca91 2020-09-20 op (defhydra hydra-emms (:hint nil)
123 9df6ca91 2020-09-20 op "
124 9df6ca91 2020-09-20 op %(my/emms-player-status) %(emms-track-description (emms-playlist-current-selected-track))
125 9df6ca91 2020-09-20 op
126 9df6ca91 2020-09-20 op ^Volume^ ^Controls^ ^Playback^ ^Misc^
127 9df6ca91 2020-09-20 op ^^^^^^^^----------------------------------------------------------------
128 9df6ca91 2020-09-20 op _+_: inc _n_: next _r_: repeat one ;%(my/active-p emms-repeat-track) _t_oggle modeline
129 9df6ca91 2020-09-20 op _-_: dec _p_: prev _R_: repeat all ;%(my/active-p emms-repeat-playlist) _T_oggle only time
130 9df6ca91 2020-09-20 op ^ ^ _<_: seek bw _#_: shuffle _s_elect
131 9df6ca91 2020-09-20 op ^ ^ _>_: seek fw _%_: sort
132 9df6ca91 2020-09-20 op ^ ^ _SPC_: play/pause
133 9df6ca91 2020-09-20 op ^ ^ _DEL_: restart
134 9df6ca91 2020-09-20 op "
135 9df6ca91 2020-09-20 op ("+" emms-volume-raise)
136 9df6ca91 2020-09-20 op ("-" emms-volume-lower)
137 9df6ca91 2020-09-20 op ("n" emms-next)
138 9df6ca91 2020-09-20 op ("p" emms-previous)
139 9df6ca91 2020-09-20 op ("<" emms-seek-backward)
140 9df6ca91 2020-09-20 op (">" emms-seek-forward)
141 9df6ca91 2020-09-20 op ("SPC" emms-pause)
142 9df6ca91 2020-09-20 op ("DEL" (emms-player-seek-to 0))
143 9df6ca91 2020-09-20 op ("<backspace>" (emms-player-seek-to 0))
144 9df6ca91 2020-09-20 op ("r" emms-toggle-repeat-track)
145 9df6ca91 2020-09-20 op ("R" emms-toggle-repeat-playlist)
146 9df6ca91 2020-09-20 op ("#" emms-shuffle)
147 9df6ca91 2020-09-20 op ("%" emms-sort)
148 9df6ca91 2020-09-20 op ("t" (progn (my/emms-toggle-time-display)
149 9df6ca91 2020-09-20 op (emms-mode-line-toggle)))
150 9df6ca91 2020-09-20 op ("T" my/emms-toggle-time-display)
151 9df6ca91 2020-09-20 op ("s" my/selectrum-emms)
152 9df6ca91 2020-09-20 op
153 9df6ca91 2020-09-20 op ("q" nil :exit t))
154 9df6ca91 2020-09-20 op ```
155 9df6ca91 2020-09-20 op
156 9df6ca91 2020-09-20 op ## tag support
157 9df6ca91 2020-09-20 op
158 9df6ca91 2020-09-20 op I compiled the companion program `emms-print-metadata` to have tag
159 9df6ca91 2020-09-20 op support. Tags are read from a cache or using the helper program, so
160 9df6ca91 2020-09-20 op don't get frustrated if your tags aren't updated: clear the cache and
161 9df6ca91 2020-09-20 op re-import the music.
162 9df6ca91 2020-09-20 op
163 9df6ca91 2020-09-20 op To use the helper program one needs to
164 9df6ca91 2020-09-20 op
165 9df6ca91 2020-09-20 op ```elisp
166 9df6ca91 2020-09-20 op (require 'emms-info)
167 9df6ca91 2020-09-20 op (require 'emms-info-libtag)
168 9df6ca91 2020-09-20 op (setq emms-info-functions '(emms-info-libtag))
169 9df6ca91 2020-09-20 op ```
170 9df6ca91 2020-09-20 op
171 9df6ca91 2020-09-20 op ## OpenBSD-specific
172 9df6ca91 2020-09-20 op
173 9df6ca91 2020-09-20 op EMMS should use `mixerctl(1)` to adjust the volume on OpenBSD. While
174 9df6ca91 2020-09-20 op it should work out of the box, `mixerctl` is considered too low level
175 9df6ca91 2020-09-20 op for normal user, and should be available only for root from 6.8
176 9df6ca91 2020-09-20 op onwards.
177 9df6ca91 2020-09-20 op
178 9df6ca91 2020-09-20 op So, here's a `emms-volume-sndioctl.el` that works. It's basic, and
179 9df6ca91 2020-09-20 op should be probably polished a little bit more.
180 9df6ca91 2020-09-20 op
181 9df6ca91 2020-09-20 op ```elisp
182 9df6ca91 2020-09-20 op ;;; emms-volume-sndioctl.el --- a mode for changing volume using sndioctl
183 9df6ca91 2020-09-20 op
184 9df6ca91 2020-09-20 op ;; This file is NOT part of EMMS.
185 9df6ca91 2020-09-20 op
186 9df6ca91 2020-09-20 op ;;;###autoload
187 9df6ca91 2020-09-20 op (defun emms-volume-sndioctl-change (amount)
188 9df6ca91 2020-09-20 op "Change sndioctl output.level by AMOUNT."
189 9df6ca91 2020-09-20 op (message "Playback channels: %s"
190 9df6ca91 2020-09-20 op (with-temp-buffer
191 9df6ca91 2020-09-20 op (when (zerop
192 9df6ca91 2020-09-20 op (call-process "sndioctl" nil (current-buffer) nil
193 9df6ca91 2020-09-20 op "-n"
194 9df6ca91 2020-09-20 op (format "output.level=%s%f"
195 9df6ca91 2020-09-20 op (if (> amount 0) "+" "")
196 9df6ca91 2020-09-20 op (/ (float amount) 100))))
197 9df6ca91 2020-09-20 op (replace-regexp-in-string "\n" "" (buffer-string))))))
198 9df6ca91 2020-09-20 op
199 9df6ca91 2020-09-20 op (provide 'emms-volume-sndioctl)
200 9df6ca91 2020-09-20 op ```
201 9df6ca91 2020-09-20 op
202 9df6ca91 2020-09-20 op Leave it somewhere in your `load-path`, `~/.emacs.d/lisp/` is a good
203 9df6ca91 2020-09-20 op place, and then add to your configuration
204 9df6ca91 2020-09-20 op
205 9df6ca91 2020-09-20 op ```elisp
206 9df6ca91 2020-09-20 op (require 'emms-volume-sndioctl)
207 9df6ca91 2020-09-20 op (setq-default emms-volume-change-function 'emms-volume-sndioctl-change)
208 9df6ca91 2020-09-20 op ```