;;; emms-init.el --- Initialize emms

;; This file is part of Michael Olson's Emacs settings.

;; The code in this file may be used, distributed, and modified
;; without restriction.

;; I use initsplit.el to separate customize settings on a per-project
;; basis.

;;; Setup

(add-to-list 'load-path "/home/mwolson/proj/emacs/emms/emms2")

;; Initialize
(require 'emms)
(require 'emms-browser)
(require 'emms-cache)
(require 'emms-info)
(require 'emms-lastfm)
(require 'emms-player-mpd)
(require 'emms-playing-time)
(require 'emms-playlist-mode)
(require 'emms-streams)
(require 'emms-tag-editor)
(require 'emms-volume)

(emms-cache 1)
(emms-playing-time 1)
(emms-lastfm 1)                    ; must come after emms-playing-time

;; Load authentication info from an external source
(load "~/.emacs.d/.emms-auth")

;; The name of emms-playlist-mode is *way* too long
(add-hook 'emms-playlist-mode-hook
          #'(lambda ()
              (setq mode-name "EMMS")))

;; Only show files in emms-browser, not playlists
(emms-browser-make-filter
 "all-files" (emms-browser-filter-only-type 'file))
(emms-browser-set-filter (assoc "all-files" emms-browser-filters))

;;; Helper functions

(defun my-upcase-initials (string)
  "Do `upcase-initials' on STRING, but do not uppercase letters
that come after quote characters."
  (with-temp-buffer
    (insert (upcase-initials string))
    (goto-char (point-min))
    (while (re-search-forward "['`]\\([[:upper:]]\\)" nil t)
      (downcase-region (match-beginning 1) (match-end 1)))
    (buffer-string)))

(defun my-emms-info-track-description (track)
  "Return a description of the current track."
  (let ((artist (emms-track-get track 'info-artist))
        (title (emms-track-get track 'info-title))
        (name (emms-track-get track 'name))
        (type (emms-track-type track)))
    (cond ((and (stringp artist) (not (string= artist ""))
                (stringp title) (not (string= title "")))
           (let ((desc (format "%s - %s" artist title)))
             (if (memq type '(file url))
                 desc
               (concat (symbol-name type) ": " desc))))
          ((and (stringp title) (not (string= title "")))
           (my-upcase-initials title))
          ((null name)
           "Invalid track!")
          ((memq type '(url streamlist))
           "")
          (t
           (concat (symbol-name type) ": " name)))))

(defun my-emms-info-fromname (track)
  "Add track information to TRACK.
This is a useful element for `emms-info-functions'."
  (when (and (memq (emms-track-type track) '(file playlist))
             (not (or (emms-track-get track 'info-artist)
                      (emms-track-get track 'info-title))))
    (let ((name (emms-track-get track 'name))
          artist title)
      (unless (or (null name) (string-match "\\`http://" name))
        (setq name (file-name-sans-extension (file-name-nondirectory name)))
        (save-match-data
          (when (string-match "\\`\\([^-]+?\\) *- *\\(.*\\)\\'" name)
            (setq artist (match-string 1 name)
                  title (match-string 2 name))
            (setq artist (my-upcase-initials
                          (emms-replace-regexp-in-string "_" " " artist)))
            (setq title (my-upcase-initials
                         (emms-replace-regexp-in-string "_" " " title)))
            (emms-track-set track 'info-artist artist)
            (emms-track-set track 'info-title title)))))))

;;; Utilities

;; Show the currently-playing song
(defun np (&optional arg)
  (interactive "P")
  (emms-player-mpd-show arg))

;; Reset the cached info for all songs in the current buffer
(defun my-emms-reset-cache ()
  (interactive)
  (save-excursion
    (goto-char (point-min))
    (emms-walk-tracks
      (let ((track (emms-playlist-track-at (point))))
        (emms-cache-set (emms-track-get track 'type)
                        (emms-track-get track 'name)
                        nil)))))

;; Open file at point in playlist
(defun my-emms-playlist-open-file ()
  (interactive)
  (let ((name (emms-track-get (emms-playlist-track-at) 'name)))
    (when name
      (find-file name))))

;; Switch to the radio buffer
(defun my-emms-streams ()
  (interactive)
  (let ((buf (get-buffer emms-stream-buffer-name)))
    (if buf
        (switch-to-buffer buf)
      (emms-streams))))

;; Switch to either the radio buffer or the current EMMS playlist
(defun my-emms-switch-to-current-playlist ()
  (interactive)
  (if (and (boundp 'emms-stream-playlist-buffer)
           (eq emms-stream-playlist-buffer emms-playlist-buffer))
      (switch-to-buffer emms-stream-buffer-name)
    (if (or (null emms-playlist-buffer)
            (not (buffer-live-p emms-playlist-buffer)))
        (error "No current Emms buffer")
      (switch-to-buffer emms-playlist-buffer))))

;; Bring up the browser interface with my preferences
(defvar my-emms-browser-buffer nil)

(defun my-emms-switch-to-browser ()
  (interactive)
  (if (and my-emms-browser-buffer
           (buffer-live-p my-emms-browser-buffer))
      (switch-to-buffer my-emms-browser-buffer)
    (emms-browse-by-artist)
    (emms-browser-expand-to-level-2)
    (setq my-emms-browser-buffer (current-buffer)))
  (isearch-forward))

;;; Every time the song changes, show me its description

(defvar np-interval 5
  "How often (in seconds) to check whether the current song has changed.")

;; Internal
(defvar np-last nil
  "Description for most recently-played track.")
(defvar np-timer nil
  "Timer used by `np-maybe'.")

(defun np-maybe ()
  (condition-case nil
      (emms-player-mpd-show
       nil
       (lambda (buffer desc)
         (when (and (stringp desc)
                    (not (string= desc "")))
           (unless (and (stringp np-last)
                        (string= np-last desc))
             (setq np-last desc)
             (message "%s" desc)))))
    (error (np-remove))))

(defun np-insinuate ()
  (unless np-timer
    (setq np-timer (run-at-time t np-interval #'np-maybe))))

(defun np-remove ()
  (when np-timer
    (emms-cancel-timer np-timer)
    (setq np-timer nil)))

(add-hook 'emms-player-started-hook #'np-insinuate)
(add-hook 'emms-player-stopped-hook #'np-remove)
(add-hook 'emms-player-finished-hook #'np-remove)

;;; Key customizations

(define-key emms-playlist-mode-map "o" 'my-emms-playlist-open-file)
(define-key emms-playlist-mode-map "q" 'emms-playlist-current-kill)
(define-key emms-playlist-mode-map (kbd "SPC") 'emms-pause)

(global-set-key "\C-cm" nil)
(global-set-key "\C-cm " 'emms-pause)
(global-set-key "\C-cm0" 'emms-playlist-current-clear)
(global-set-key "\C-cmb" 'my-emms-switch-to-browser)
(global-set-key "\C-cmc" 'emms-player-mpd-connect)
(global-set-key "\C-cml" nil)
(global-set-key "\C-cmlb" 'emms-lastfm-radio-ban)
(global-set-key "\C-cmll" 'emms-lastfm-radio-love)
(global-set-key "\C-cmls" 'emms-lastfm-radio-skip)
(global-set-key "\C-cmn" 'emms-player-mpd-next)
(global-set-key "\C-cmo" 'my-emms-switch-to-current-playlist)
(global-set-key "\C-cmp" 'emms-player-mpd-previous)
(global-set-key "\C-cmq" 'emms-stop)
(global-set-key "\C-cmQ" #'(lambda () (interactive) (emms-player-mpd-clear)))
(global-set-key "\C-cmr" 'my-emms-streams)
(global-set-key "\C-cms" 'emms-seek)
(global-set-key "\C-cm-" 'emms-volume-mode-minus)
(global-set-key "\C-cm+" 'emms-volume-mode-plus)
(global-set-key "\C-cm\C-c" 'my-emms-reset-cache)

;;; Custom variables

(custom-set-variables
 '(emms-cache-file "~/.emacs.d/.emms-cache")
 '(emms-info-asynchronously nil)
 '(emms-info-functions (quote (emms-info-mpd my-emms-info-fromname)))
 '(emms-lastfm-radio-metadata-period nil)
 '(emms-player-list (quote (emms-player-mpd emms-player-lastfm-radio)))
 '(emms-player-mpd-music-directory "/stuff/music")
 '(emms-player-mpd-verbose t)
 '(emms-playlist-buffer-name "*EMMS Playlist*")
 '(emms-playlist-default-major-mode (quote emms-playlist-mode))
 '(emms-playlist-mode-open-playlists t)
 '(emms-show-format "NP: %s")
 '(emms-stream-default-action "play")
 '(emms-track-description-function (quote my-emms-info-track-description))
 '(emms-track-initialize-functions (quote (emms-info-initialize-track)))
 '(emms-volume-change-amount 1)
 '(emms-volume-change-function (quote emms-volume-mpd-change)))
(custom-set-faces)

;;; Initialization

;;; (define-emms-combined-source all nil
;;;   '((emms-source-playlist-directory "/stuff/music/albums")
;;;     (emms-source-playlist-directory "/stuff/music/albums/classical")
;;;     (emms-source-playlist-directory "/stuff/music/albums/candy_mind")
;;;     (emms-source-playlist-directory "/stuff/music/mixes")
;;;     (emms-source-playlist-file "~/.emacs.d/emms-playlist")))

;;; (emms-add-all)

;;; emms-init.el ends here