Blob


1 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)
3 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.
5 One thing that I really appreciate about EMMS is how minimal it is: there is only one interface, the playlist buffer, and nothing else.
7 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.
9 ## Setup
11 I'm not doing some fancy stuff to set it up. I'm just doing what suggested in the documentation:
13 ```
14 (require 'emms-setup)
15 (emms-all)
16 (emms-default-players)
18 ;; fancy
19 (setq emms-mode-line-format "「%s」")
20 ```
22 ## Select a song
24 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.
26 ```
27 (defun my/selectrum-emms ()
28 "select and play a song from the current emms playlist."
29 (interactive)
30 (with-current-emms-playlist
31 (emms-playlist-mode-center-current)
32 (let* ((selectrum-should-sort-p nil)
33 (current-line-number (line-number-at-pos (point)))
34 (lines (cl-loop
35 with min-line-number = (line-number-at-pos (point-min))
36 with buffer-text-lines = (split-string (buffer-string) "\n")
37 with lines = nil
38 for l in buffer-text-lines
39 for n = min-line-number then (1+ n)
40 do (push (propertize l' line-num n)
41 lines)
42 finally return (nreverse lines)))
43 (selected-line (selectrum-read "song: " lines
44 :default-candidate (nth (1- current-line-number) lines)
45 :require-match t
46 :no-move-default-candidate t)))
47 (when selected-line
48 (let ((line (get-text-property 0 'line-num selected-line)))
49 (goto-line line)
50 (emms-playlist-mode-play-smart)
51 (emms-playlist-mode-center-current))))))
52 ```
54 => /img/emms-selectrum.png Selecting a song from the current playlist with selectrum
56 Should be pretty easy to adapt it to use the standard completing-read.
58 ## Hydra
60 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.
62 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).
64 => /img/emms-hydra.png An hydra for EMMS
66 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.
68 First, some helper functions
70 ```
71 (defun my/tick-symbol (x)
72 "Return a tick if X is true-ish."
73 (if x "x" " "))
75 (defun my/emms-player-status ()
76 "Return the state of the EMMS player: `not-active', `playing', `paused' or `dunno'.
78 Modeled after `emms-player-pause'."
79 (cond ((not emms-player-playing-p)
80 ;; here we should return 'not-active. The fact is that
81 ;; when i change song, there is a short amount of time
82 ;; where we are ``not active'', and the hydra is rendered
83 ;; always during that short amount of time. So we cheat a
84 ;; little.
85 'playing)
87 (emms-player-paused-p
88 (let ((resume (emms-player-get emms-player-playing-p 'resume))
89 (pause (emms-player-get emms-player-playing-p 'pause)))
90 (cond (resume 'paused)
91 (pause 'playing)
92 (t 'dunno))))
93 (t (let ((pause (emms-player-get emms-player-playing-p 'pause)))
94 (if pause 'playing 'dunno)))))
96 (defun my/emms-toggle-time-display ()
97 "Toggle the display of time information in the modeline"
98 (interactive "")
99 (if emms-playing-time-display-p
100 (emms-playing-time-disable-display)
101 (emms-playing-time-enable-display)))
102 ```
104 and then the full hydra
106 ```
107 (defhydra hydra-emms (:hint nil)
109 %(my/emms-player-status) %(emms-track-description (emms-playlist-current-selected-track))
111 ^Volume^ ^Controls^ ^Playback^ ^Misc^
112 ^^^^^^^^----------------------------------------------------------------
113 _+_: inc _n_: next _r_: repeat one [% s(my/tick-symbol emms-repeat-track)] _t_oggle modeline
114 _-_: dec _p_: prev _R_: repeat all [% s(my/tick-symbol emms-repeat-playlist)] _T_oggle only time
115 ^ ^ _<_: seek bw _#_: shuffle _s_elect
116 ^ ^ _>_: seek fw _%_: sort _g_oto EMMS buffer
117 ^ ^ _SPC_: play/pause
118 ^ ^ _DEL_: restart
120 ("+" emms-volume-raise)
121 ("-" emms-volume-lower)
122 ("n" emms-next)
123 ("p" emms-previous)
124 ("<" emms-seek-backward)
125 (">" emms-seek-forward)
126 ("SPC" emms-pause)
127 ("DEL" (emms-player-seek-to 0))
128 ("<backspace>" (emms-player-seek-to 0))
129 ("r" emms-toggle-repeat-track)
130 ("R" emms-toggle-repeat-playlist)
131 ("#" emms-shuffle)
132 ("%" emms-sort)
133 ("t" (progn (my/emms-toggle-time-display)
134 (emms-mode-line-toggle)))
135 ("T" my/emms-toggle-time-display)
136 ("s" my/selectrum-emms)
137 ("g" (progn (emms)
138 (with-current-emms-playlist
139 (emms-playlist-mode-center-current))))
141 ("q" nil :exit t))
142 ```
144 ## Tag support
146 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.
148 To use the helper program one needs to
150 ```
151 (require 'emms-info)
152 (require 'emms-info-libtag)
153 (setq emms-info-functions '(emms-info-libtag))
154 ```
156 ## OpenBSD-specific
158 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.
160 So, here's a emms-volume-sndioctl.el that works. It's basic, and should be probably polished a little bit more.
162 ```
163 ;;; emms-volume-sndioctl.el --- a mode for changing volume using sndioctl
165 ;; This file is NOT part of EMMS.
167 ;;;###autoload
168 (defun emms-volume-sndioctl-change (amount)
169 "Change sndioctl output.level by AMOUNT."
170 (message "Playback channels: %s"
171 (with-temp-buffer
172 (when (zerop
173 (call-process "sndioctl" nil (current-buffer) nil
174 "-n"
175 (format "output.level=%s%f"
176 (if (> amount 0) "+" "")
177 (/ (float amount) 100))))
178 (replace-regexp-in-string "\n" "" (buffer-string))))))
180 (provide 'emms-volume-sndioctl)
181 ```
183 Leave it somewhere in your load-path, ~/.emacs.d/lisp/ is a good place, and then add to your configuration
185 ```
186 (require 'emms-volume-sndioctl)
187 (setq-default emms-volume-change-function 'emms-volume-sndioctl-change)
188 ```