Blame


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