X-Git-Url: https://thelambdalab.xyz/gitweb/index.cgi?p=emus.git;a=blobdiff_plain;f=emus.el;h=cfa674a86ea8b49a1142003d5b40738dbeb3d607;hp=eb3d56645536179ce2aa3a00d21cb84ef3941ca7;hb=HEAD;hpb=6efc84e4c57ecfddcc3d8fcb0327c55af0e1536b diff --git a/emus.el b/emus.el index eb3d566..e5f2ea3 100644 --- a/emus.el +++ b/emus.el @@ -1,17 +1,46 @@ -;;; emus.el --- Simple music player for Emacs. -*- lexical-binding:t -*- +;;; emus.el --- Simple mp3 player -*- lexical-binding:t -*- + +;; Copyright (C) 2019 Tim Vaughan ;; Author: Tim Vaughan +;; Created: 8 December 2019 ;; Version: 1.0 ;; Keywords: multimedia -;; URL: https://thelambdalab.xyz/emus +;; Homepage: http://thelambdalab.xy/emus +;; Package-Requires: ((emacs "26")) + +;; This file is not part of GNU Emacs. + +;; This program is free software: you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this file. If not, see . ;;; Commentary: -;; This is a simple package for playing audio from a local library -;; of audio files. +;; This is a simple package for playing audio from a local directory +;; tree of mp3 files. It uses the program mpg123 as its back-end. +;; Currently the library is loaded completely every time emus starts. ;;; Code: +(provide 'emus) + + +;;; Dependencies +;; + +(require 'seq) + + ;;; Customizations ;; @@ -27,67 +56,66 @@ "Name of (and, optionally, path to) mpg123 binary." :type '(string)) -;;; Library -;; +(defcustom emus-mpg123-args nil + "Arguments to pass to mpg123." + :type '(repeat string)) + +(defface emus-artist + '((((background dark)) :inherit font-lock-string-face :background "#222" :extend t) + (t :inherit font-lock-string-face :background "#ddd" :extend t)) + "Face used for artist names in browser.") + +(defface emus-album + '((((background dark)) :inherit font-lock-constant-face :background "#111" :extend t) + (t :inherit font-lock-constant-face :background "#eee" :extend t)) + "Face used for album names in browser.") -(defvar emus-library nil +(defface emus-track + '((t :inherit font-lock-keyword-face)) + "Face used for track titles in browser.") + +(defface emus-track-current + '((t :inherit font-lock-keyword-face :inverse-video t :extend t)) + "Face used for track titles in browser.") + +(defface emus-cursor + '((t :inherit bold)) + "Face used for current track cursor") + + +;;; Global variables + +(defvar emus--proc-in-use nil + "If non-nil, disables `emus-send-cmd'. +Used to prevent commands from interfering with library construction.") + +(defvar emus-tracks nil "Emus audio library.") -(defun emus-get-audio-files () - "Get all mp3 files in main emus directory." - (directory-files-recursively emus-directory ".*\\.mp3")) +(defvar emus-current-track nil + "Currently-selected emus track.") -(defvar emus-records nil) +(defvar emus-state 'stopped + "Current playback state.") -(defun emus-make-record (filename tagstr) - (let ((artist "") - (album "") - (title "")) - (dolist (line (split-string tagstr "\n")) - (let ((found-artist (elt (split-string line "@I ID3v2.artist:") 1)) - (found-album (elt (split-string line "@I ID3v2.album:") 1)) - (found-title (elt (split-string line "@I ID3v2.title:") 1))) - (cond - (found-artist (setq artist found-artist)) - (found-album (setq album found-album)) - (found-title (setq title found-title))))) - (list artist album title filename))) - -(defun emus-record-artist (record) - (elt record 0)) - -(defun emus-record-album (record) - (elt record 1)) - -(defun emus-record-title (record) - (elt record 2)) - -(defun emus-record-file (record) - (elt record 3)) - -(defun emus-update-records (then) - (let ((proc (emus-get-process)) - (tagstr "") - (filenames (emus-get-audio-files))) - (setq emus-records nil) - (set-process-filter proc (lambda (proc string) - (setq tagstr (concat tagstr string)) - (when (string-suffix-p "@P 1\n" string) - (add-to-list 'emus-records - (emus-make-record (car filenames) - tagstr)) - (setq tagstr "") - (setq filenames (cdr filenames)) - (if filenames - (emus-send-cmd "lp" (car filenames)) - (set-process-filter proc nil) - (funcall then))))) - (emus-send-cmd "lp" (car filenames)))) +(defvar emus-continuous-playback t + "If non-nil, emus will automatically play the next track when the current track is finished.") + +(defvar emus-current-volume 100 + "The current playback volume.") + +(defvar emus-current-progress "" + "String describing the progress through the current track.") + +(defvar emus-progress-enabled t + "Current state of progress tracking. + +To enable or disable progress tracking, using `emus-toggle-progress-tracking'. +(Changing the value of this variable will not affect anything.)") ;;; mpg123 process ;; -(defvar emus-proc-in-use nil) (defun emus-get-process () "Return current or new mpg123 process." @@ -99,115 +127,703 @@ nil)))) (if emus-process emus-process - (make-process :name "emus-process" - ;; :buffer (get-buffer-create "*emus-process*") - :command `(,emus-mpg123-program "-R"))))) + (let ((proc + (make-process :name "emus-process" + :command `(,emus-mpg123-program + ,@emus-mpg123-args + "-R")))) + (set-process-query-on-exit-flag proc nil) + (unless emus-progress-enabled + (process-send-string proc "silence\n")) + proc)))) + +(defun emus--send-cmd-raw (cmd &rest args) + "Send a command CMD with args ARGS to the mpg123 process. +This procedure does not respect `emus--proc-in-use' and thus should only +be used by `emus--load-library'." + (process-send-string (emus-get-process) + (concat + (seq-reduce (lambda (s1 s2) (concat s1 " " s2)) args cmd) + "\n"))) (defun emus-send-cmd (cmd &rest args) - (process-send-string (emus-get-process) - (concat - (seq-reduce (lambda (s1 s2) (concat s1 " " s2)) args cmd) - "\n"))) + "Send a command CMD with args ARGS to the mpg123 process." + (unless emus--proc-in-use + (apply #'emus--send-cmd-raw cmd args))) -;;; Playback +(defun emus-kill-process () + "Kill any existing mpg123 process." + (let ((emus-process (get-process "emus-process"))) + (if emus-process + (kill-process emus-process)) + (setq emus-state 'stopped + emus--proc-in-use nil + emus-tracks nil))) + +;;; Library ;; -(defvar emus-currently-playing nil) +(defun emus-get-audio-files () + "Get all mp3 files in main emus directory." + (mapcar + #'expand-file-name + (directory-files-recursively emus-directory ".*\\.mp3"))) + +(defun emus-get-playlist-files () + "Get all m3u files in the main emus music directory." + (mapcar + #'expand-file-name + (directory-files-recursively emus-directory ".*\\.m3u"))) + +(defun emus-make-track (artist album title filename &optional pos) + "Create an object representing an emus track. +ARTIST, ALBUM and TITLE are used to describe the track, FILENAME +refers to the mp3 file containing the track. If non-nil, POS +specifies the position of the record representing this track in the +emus browser buffer." + (list artist album title filename pos)) + +(defun emus-track-artist (track) + "The artist corresponding to TRACK." + (elt track 0)) + +(defun emus-track-album (track) + "The album corresponding to TRACK." + (elt track 1)) + +(defun emus-track-title (track) + "The title of TRACK." + (elt track 2)) + +(defun emus-track-file (track) + "The mp3 file corresponding to TRACK." + (elt track 3)) + +(defun emus-track-browser-pos (track) + "The location of the browser buffer record corresponding to TRACK." + (elt track 4)) + +(defun emus-set-track-browser-pos (track pos) + "Set the location of the browser buffer record corresponding to TRACK to POS." + (setf (seq-elt track 4) pos)) + +(defun emus--load-library (then) + "Initialize the emus track library. +Once the library is initialized, the function THEN is called." + (unless emus--proc-in-use + (setq emus--proc-in-use t) + (emus--suspend-cp) + (setq emus-state 'stopped) + (setq emus-tracks nil) + (let ((proc (emus-get-process)) + (tagstr "") + (filenames (emus-get-audio-files))) + (set-process-filter proc (lambda (proc string) + (setq tagstr (concat tagstr string)) + (when (string-suffix-p "@P 1\n" string) + (add-to-list 'emus-tracks + (emus--make-track-from-tagstr (car filenames) + tagstr)) + (setq tagstr "") + (setq filenames (cdr filenames)) + (if filenames + (emus--send-cmd-raw "lp" (car filenames)) + (set-process-filter proc nil) + (setq emus-tracks (reverse emus-tracks)) + (emus--sort-tracks) + (emus--add-tracks-from-playlist-files) + (unless emus-current-track + (setq emus-current-track (car emus-tracks))) + (funcall then) + (emus--resume-cp) + (setq emus--proc-in-use nil))))) + (emus--send-cmd-raw "lp" (car filenames))))) + +(defun emus--dump-tracks () + emus-tracks) + +(defun emus--make-track-from-tagstr (filename tagstr) + "Parse TAGSTR to populate the fields of a track corresponding to FILENAME." + (let ((artist "Unknown Artist") + (album "Unknown Album") + (title filename)) + (dolist (line (split-string tagstr "\n")) + (let ((found-artist (elt (split-string line "@I ID3v2.artist:") 1)) + (found-album (elt (split-string line "@I ID3v2.album:") 1)) + (found-title (elt (split-string line "@I ID3v2.title:") 1))) + (cond + (found-artist (setq artist found-artist)) + (found-album (setq album found-album)) + (found-title (setq title found-title))))) + (emus-make-track artist album title filename))) + +(defun emus--add-tracks-from-playlist-files () + (let ((tracks nil)) + (dolist (filename (emus-get-playlist-files)) + (let ((artist "Playlists") + (album (file-name-base filename)) + (title nil) + (lines (split-string (with-temp-buffer + (insert-file-contents filename) + (buffer-string)) + "\n"))) + (dolist (line lines) + (pcase (string-trim line) + ((rx (: string-start + (* space) + "#extinf:" + (* (not ",")) "," + (let display-title (* any)) + string-end)) + (setq title display-title)) + ((rx (: string-start (* space) "#"))) ;skip other comments + ((rx (let filename (+ any))) + (setq tracks (cons (emus-make-track artist album (or title filename) filename) + tracks)) + (setq title nil)))))) + (setq emus-tracks (append emus-tracks (reverse tracks))))) + +(defun emus--sort-tracks () + "Sort the library tracks according to artist and album. +Leaves the track titles unsorted, so they will appear in the order specified +by the filesystem." + (setq emus-tracks + (sort emus-tracks + (lambda (r1 r2) + (let ((artist1 (emus-track-artist r1)) + (artist2 (emus-track-artist r2))) + (if (string= artist1 artist2) + (let ((album1 (emus-track-album r1)) + (album2 (emus-track-album r2))) + (string< album1 album2)) + (string< artist1 artist2))))))) + +(defmacro emus--with-library (&rest body) + "Evaluate BODY with the library initialized." + `(if emus-tracks + (unless emus--proc-in-use ,@body) + (emus--load-library + (lambda () ,@body)))) + -(defun emus-play-record (record) - (setq emus-currently-playing record) - (emus-send-cmd "l" (emus-record-file record))) +;;; Playback +;; + +(defun emus--suspend-cp () + "Suspend continuous playback." + (setq emus-continuous-playback nil)) + +(defun emus--resume-cp () + "Resume continuous playback." + (setq emus-continuous-playback t)) + +(defun emus--timestamp (seconds-total) + "Produce a timestamp string representation of SECONDS-TOTAL." + (let* ((seconds (truncate (mod seconds-total 60))) + (minutes (truncate (/ seconds-total 60)))) + (format "%02d:%02d" minutes seconds))) + +(defun emus-play-track (track) + "Set TRACK as current and start playing." + (emus--with-library + (let ((old-track emus-current-track)) + (emus-send-cmd "l" (emus-track-file track)) + (setq emus-state 'playing) + (setq emus-current-track track) + (setq emus-current-progress (if emus-progress-enabled "" " (progress disabled)")) + (set-process-filter + (emus-get-process) + (lambda (_proc string) + (dolist (line (string-split string "\n")) + (pcase line + ((and "@P 0" + (guard emus-continuous-playback) + (guard (eq emus-state 'playing))) + (emus-play-next)) + ((and (guard emus-progress-enabled) + (rx (: string-start + "@I ICY-META: StreamTitle=" + (let str (+ (not ";"))) + ";"))) + (message (concat "Emus: Playing stream " str))) + ((and (guard emus-progress-enabled) + (rx (: string-start + "@F " + (+ digit) + " " + (+ digit) + " " + (let left-str (+ (not " "))) + " " + (let right-str (+ any))))) + (let* ((left (string-to-number left-str)) + (right (string-to-number right-str)) + (total (+ left right))) + (setq emus-current-progress + (format " %s/%s" + (emus--timestamp left) + (emus--timestamp total))))) + )))) + (emus--update-track old-track) + (emus--update-track track) + (emus--resume-cp) + (emus-goto-current)))) + +(defun emus-select-track (track) + "Set TRACK as current, but do not start playing." + (emus--with-library + (let ((old-track emus-current-track)) + (setq emus-state 'stopped) + (setq emus-current-track track) + (emus--update-track old-track) + (emus--update-track track) + (emus-send-cmd "o") + (emus--resume-cp) + (emus-goto-current)))) (defun emus-stop () + "Stop playback of the current track." (interactive) - (emus-send-cmd "s") - (setq emus-currently-playing nil)) + (emus--with-library + (setq emus-state 'stopped) + (emus--update-track emus-current-track) + (emus-send-cmd "s"))) (defun emus-playpause () + "Begin playback of the current track. +If the track is already playing, pause playback. +If the track is currently paused, resume playback." + (interactive) + (emus--with-library + (when emus-current-track + (if (eq emus-state 'stopped) + (emus-play-track emus-current-track) + (emus-send-cmd "p") + (pcase emus-state + ((or 'paused 'stopped) (setq emus-state 'playing)) + ('playing (setq emus-state 'paused))) + (unless (eq emus-state 'paused))) + (emus--update-track emus-current-track)))) + +(defun emus-set-volume (pct) + "Set the playback volume to PCT %." + (emus--with-library + (setq emus-current-volume pct) + (emus-send-cmd "v" (number-to-string pct)))) + +(defun emus-volume-increase-by (delta) + "Increase the playback volume by DELTA %." + (emus-set-volume (max 0 (min 100 (+ emus-current-volume delta))))) + +(defun emus-volume-up () + "Increase the playback volume by 10%." + (interactive) + (emus-volume-increase-by 10)) + +(defun emus-volume-down () + "Decrease the playback volume by 10%." (interactive) - (emus-send-cmd "p")) + (emus-volume-increase-by -10)) + +(defun emus--play-adjacent-track (&optional prev) + "Play the next track in the library, or the previous if PREV is non-nil." + (emus--with-library + (let ((idx (seq-position emus-tracks emus-current-track)) + (offset (if prev -1 +1))) + (if idx + (let ((next-track (elt emus-tracks (+ idx offset)))) + (if next-track + (if (eq emus-state 'playing) + (emus-play-track next-track) + (emus-select-track next-track)) + (error "Track does not exist"))) + (error "No track selected"))))) + +(defun emus--play-adjacent-album (&optional prev) + "Play the first track of the next album in the library. +If PREV is non-nil, plays the last track of the previous album." + (emus--with-library + (let ((idx (seq-position emus-tracks emus-current-track))) + (if idx + (let* ((search-list (if prev + (reverse (seq-subseq emus-tracks 0 idx)) + (seq-subseq emus-tracks (+ idx 1)))) + (current-album (emus-track-album emus-current-track)) + (next-track (seq-some (lambda (r) + (if (string= (emus-track-album r) + current-album) + nil + r)) + search-list))) + (if next-track + (if (eq emus-state 'playing) + (emus-play-track next-track) + (emus-select-track next-track)) + (error "Track does not exist"))) + (error "No track selected"))))) + +(defun emus-play-next () + "Play the next track in the library." + (interactive) + (emus--play-adjacent-track)) +(defun emus-play-prev () + "Play the previous track in the library." + (interactive) + (emus--play-adjacent-track t)) -(defun emus-volume (pct) - (emus-send-cmd "v" (number-to-string pct))) +(defun emus-play-next-album () + "Play the first track of the next album in the library." + (interactive) + (emus--play-adjacent-album)) -(defvar emus-current-volume 10) +(defun emus-play-prev-album () + "Play the last track of the previous album in the library." + (interactive) + (emus--play-adjacent-album t)) -(defun emus-volume-delta (delta) - (setq emus-current-volume (max 0 (min 100 (+ emus-current-volume delta)))) - (emus-volume emus-current-volume)) +(defun emus-jump (seconds) + "Jump forward in current track by SECONDS seconds." + (emus--with-library + (emus-send-cmd "jump" (format "%+ds" seconds)))) -(defun emus-volume-up () +(defun emus-jump-10s-forward () + "Jump 10 seconds forward in current track." (interactive) - (emus-volume-delta 10)) + (emus-jump 10)) -(defun emus-volume-down () +(defun emus-jump-10s-backward () + "Jump 10 seconds backward in current track." + (interactive) + (emus-jump -10)) + +(defun emus-jump-1m-forward () + "Jump 1 minute forward in current track." + (interactive) + (emus-jump 60)) + +(defun emus-jump-1m-backward () + "Jump 1 minute backward in current track." + (interactive) + (emus-jump -60)) + +(defun emus-display-status () + "Display the current playback status in the minibuffer." + (interactive) + (emus--with-library + (message + (concat "Emus: Volume %d%%" + (pcase emus-state + ('stopped " [Stopped]") + ('paused (format " [Paused%s]" emus-current-progress)) + ('playing (format " [Playing%s]" emus-current-progress)) + (_ "")) + " %s") + emus-current-volume + (if emus-current-track + (format "- %.30s (%.20s, %.20s)" + (emus-track-title emus-current-track) + (emus-track-album emus-current-track) + (emus-track-artist emus-current-track)) + "")))) + +(defun emus-toggle-progress-tracking () + "Enable/disable progress tracking." (interactive) - (emus-volume-delta -10)) + (setq emus-progress-enabled (not emus-progress-enabled)) + (if emus-progress-enabled + (progn + (emus-send-cmd "progress") + (setq emus-current-progress "")) + (progn + (emus-send-cmd "silence") + (setq emus-current-progress " (progress diabled)")))) ;;; Browser ;; -(defun emus-render-record (record) - (insert-text-button - (concat - (propertize (format "%-20.20s" (emus-record-artist record)) - 'face 'font-lock-keyword-face) - (propertize (format "% -20.20s" (emus-record-album record)) - 'face 'font-lock-function-name-face) - (propertize (format " %s" (emus-record-title record)) - 'face 'font-lock-string-face)) - 'action #'emus-click-record - 'follow-link t - 'emus-record record) - (insert "\n")) - -(defun emus-render-records () +(defun emus--insert-track (track &optional prev-track first) + "Insert a button representing TRACK into the current buffer. + +When provided, PREV-TRACK is used to determine whether to insert additional +headers representing the artist or the album title. + +If non-nil, FIRST indicates that the track is the first in the library +and thus requires both artist and album headers." + (let* ((artist (emus-track-artist track)) + (album (emus-track-album track)) + (title (emus-track-title track)) + (album-symb (intern (concat artist album))) + (help-str (format "mouse-1, RET: Play '%.30s' (%.20s)" title artist)) + (field (intern album))) ;Allows easy jumping between albums with cursor. + (when (or prev-track first) + (unless (equal (emus-track-artist prev-track) artist) + (insert-text-button + (propertize artist 'face 'emus-artist) + 'action #'emus--click-track + 'follow-link t + 'help-echo help-str + 'emus-track track + 'field field) + (insert (propertize "\n" + 'face 'emus-artist + 'field field))) + (unless (equal (emus-track-album prev-track) album) + (insert-text-button + (propertize (concat " " album) 'face 'emus-album) + 'action #'emus--click-track + 'follow-link t + 'help-echo help-str + 'emus-track track + 'field field) + (insert (propertize "\n" + 'face 'emus-album + 'field field)))) + (emus-set-track-browser-pos track (point)) + (let ((is-current (equal track emus-current-track))) + (insert-text-button + (concat + (if is-current + (propertize + (pcase emus-state + ('playing "->") + ('paused "-)") + ('stopped "-]")) + 'face 'emus-cursor) + (propertize " " 'face 'default)) + (propertize (format " %s" title) + 'face (if is-current + 'emus-track-current + 'emus-track))) + 'action #'emus--click-track + 'follow-link t + 'help-echo help-str + 'emus-track track + 'invisible album-symb + 'field field) + (insert (propertize "\n" + 'face (if is-current + 'emus-track-current + 'emus-track) + 'field field + 'invisible album-symb))))) + +(defun emus--update-track (track) + "Rerender entry for TRACK in emus browser buffer. +Used to update browser display when `emus-current-track' and/or `emus-state' changes." + (let ((track-pos (emus-track-browser-pos track))) + (when (and (get-buffer "*emus*") + (emus-track-browser-pos track)) + (with-current-buffer "*emus*" + (let ((inhibit-read-only t) + (old-point (point))) + (goto-char track-pos) + (search-forward "\n") + (delete-region track-pos (point)) + (goto-char track-pos) + (emus--insert-track track) + (goto-char old-point)))))) + +(defun emus--render-tracks () + "Render all library tracks in emus browser buffer." (with-current-buffer "*emus*" - (let ((inhibit-read-only t)) - (save-excursion - (erase-buffer) - (goto-char (point-min)) - (dolist (record emus-records) - (emus-render-record record)))))) - -(defun emus-click-record (button) - (emus-play-record (button-get button 'emus-record))) + (let ((inhibit-read-only t) + (old-pos (point))) + (erase-buffer) + (goto-char (point-min)) + (let ((prev-track nil)) + (dolist (track emus-tracks) + (emus--insert-track track prev-track (not prev-track)) + (setq prev-track track))) + (goto-char old-pos)))) + +(defun emus--click-track (button) + "Begin playback of track indicated by BUTTON." + (emus-play-track (button-get button 'emus-track)) + (emus-display-status)) + +(defun emus-goto-current () + "Move point to the current track in the browser buffer, if available." + (interactive) + (when (and (get-buffer "*emus*") + emus-current-track) + (with-current-buffer "*emus*" + (goto-char (emus-track-browser-pos emus-current-track))))) (defun emus-browse () "Switch to *emus* audio library browser." (interactive) - (switch-to-buffer "*emus*") - (emus-mode) - (emus-volume emus-current-volume) - (if emus-records - (emus-render-records) - (emus-update-records #'emus-render-records))) + (emus--with-library + (pop-to-buffer-same-window "*emus*") + (emus-browser-mode) + (emus--render-tracks) + (emus-goto-current))) + +(defun emus-refresh () + "Refresh the emus library." + (interactive) + (emus-stop) + (setq emus-tracks nil) + (emus-browse)) + + +;;; Playback + status display commands +;; + +(defun emus-playpause-status () + "Start, pause or resume playback, then display the emus status in the minibuffer." + (interactive) + (emus-playpause) + (emus-display-status)) + +(defun emus-stop-status () + "Stop playback, then display the emus status in the minibuffer." + (interactive) + (emus-stop) + (emus-display-status)) + +(defun emus-volume-up-status () + "Increase volume by 10%, then display the emus status in the minibuffer." + (interactive) + (emus-volume-up) + (emus-display-status)) + +(defun emus-volume-down-status () + "Decrease volume by 10%, then display the emus status in the minibuffer." + (interactive) + (emus-volume-down) + (emus-display-status)) -(defvar emus-mode-map +(defun emus-play-next-status () + "Play next track, then display the emus status in the minibuffer." + (interactive) + (emus-play-next) + (emus-display-status)) + +(defun emus-play-prev-status () + "Play previous track, then display the emus status in the minibuffer." + (interactive) + (emus-play-prev) + (emus-display-status)) + +(defun emus-play-next-album-status () + "Play first track of next album, then display the emus status in the minibuffer." + (interactive) + (emus-play-next-album) + (emus-display-status)) + +(defun emus-play-prev-album-status () + "Play last track of previous album, then display the emus status in the minibuffer." + (interactive) + (emus-play-prev-album) + (emus-display-status)) + +(defun emus-jump-10s-forward-status () + "Jump 10s forward in current track, then display the emus status in the minibuffer." + (interactive) + (emus-jump-10s-forward) + (emus-display-status)) + +(defun emus-jump-10s-backward-status () + "Jump 10s backward in current track, then display the emus status in the minibuffer." + (interactive) + (emus-jump-10s-backward) + (emus-display-status)) + +(defun emus-jump-1m-forward-status () + "Jump 10s forward in current track, then display the emus status in the minibuffer." + (interactive) + (emus-jump-1m-forward) + (emus-display-status)) + +(defun emus-jump-1m-backward-status () + "Jump 10s backward in current track, then display the emus status in the minibuffer." + (interactive) + (emus-jump-1m-backward) + (emus-display-status)) + +(defun emus-toggle-progress-status () + "Toggle progress tracking, then display the emus status in the minibuffer." + (interactive) + (emus-toggle-progress-tracking) + (emus-display-status)) + +(defun emus-goto-current-status () + "Move point to the current track, then display the emus status in the minibuffer." + (interactive) + (emus-goto-current) + (emus-display-status)) + +(defun emus-refresh-status () + "Refresh the emus library, then display the emus status in the minibuffer." + (interactive) + (emus-stop) + (setq emus-tracks nil) + (emus-browse) + (emus-display-status)) + +(defun emus-restart-browse () + "Restart the emus process, then refresh the browse window." + (interactive) + (message "Restarting mpg123.") + (emus-kill-process) + (run-at-time 0.1 nil #'emus-browse)) ;Slight delay to wait for kill signal to take effect + +(defun emus-restart-status () + "Restart the emus process, then display the status." + (interactive) + (message "Restarting mpg123.") + (emus-kill-process) + (run-at-time 0.1 nil #'emus-display-status)) ;Slight delay to wait for kill signal to take effect + + +(defvar emus-browser-mode-map (let ((map (make-sparse-keymap))) - (define-key map (kbd "SPC") 'emus-playpause) - (define-key map (kbd "o") 'emus-stop) - (define-key map (kbd "+") 'emus-volume-up) - (define-key map (kbd "=") 'emus-volume-up) - (define-key map (kbd "-") 'emus-volume-down) + (define-key map (kbd "SPC") 'emus-playpause-status) + (define-key map (kbd "o") 'emus-stop-status) + (define-key map (kbd "+") 'emus-volume-up-status) + (define-key map (kbd "=") 'emus-volume-up-status) + (define-key map (kbd "-") 'emus-volume-down-status) + (define-key map (kbd "R") 'emus-refresh-status) + (define-key map (kbd "n") 'emus-play-next-status) + (define-key map (kbd "p") 'emus-play-prev-status) + (define-key map (kbd "N") 'emus-play-next-album-status) + (define-key map (kbd "P") 'emus-play-prev-album-status) + (define-key map (kbd ",") 'emus-jump-10s-backward-status) + (define-key map (kbd ".") 'emus-jump-10s-forward-status) + (define-key map (kbd "<") 'emus-jump-1m-backward-status) + (define-key map (kbd ">") 'emus-jump-1m-forward-status) + (define-key map (kbd "c") 'emus-goto-current-status) + (define-key map (kbd "#") 'emus-toggle-progress-status) + (define-key map (kbd "!") 'emus-restart-browse) (when (fboundp 'evil-define-key*) (evil-define-key* 'motion map - (kbd "SPC") 'emus-playpause - (kbd "o") 'emus-stop - (kbd "+") 'emus-volume-up - (kbd "=") 'emus-volume-up - (kbd "-") 'emus-volume-down)) + (kbd "SPC") 'emus-playpause-status + (kbd "o") 'emus-stop-status + (kbd "+") 'emus-volume-up-status + (kbd "=") 'emus-volume-up-status + (kbd "-") 'emus-volume-down-status + (kbd "R") 'emus-refresh-status + (kbd "n") 'emus-play-next-status + (kbd "p") 'emus-play-prev-status + (kbd "N") 'emus-play-next-album-status + (kbd "P") 'emus-play-prev-album-status + (kbd ",") 'emus-jump-10s-backward-status + (kbd ".") 'emus-jump-10s-forward-status + (kbd "<") 'emus-jump-1m-backward-status + (kbd ">") 'emus-jump-1m-forward-status + (kbd "c") 'emus-goto-current-status + (kbd "#") 'emus-toggle-progress-status + (kbd "!") #'emus-restart-browse)) map) - "Keymap for emus.") + "Keymap for emus browser.") -(define-derived-mode emus-mode special-mode "Emus" - "Major mode for EMUS music player.") +(define-derived-mode emus-browser-mode special-mode "emus-browser" + "Major mode for EMUS music player file browser." + (setq-local buffer-invisibility-spec nil)) (when (fboundp 'evil-set-initial-state) - (evil-set-initial-state 'emus-mode 'motion)) - -;;; Debugging + (evil-set-initial-state 'emus-browser-mode 'motion)) ;;; emus.el ends here