separate voices directory and voice-model-directory
[emacs-rainbow-fart.git] / rainbow-fart.el
blob58a7a6c05f847d66ac9188e84f01a2aad2ee24f6
1 ;;; rainbow-fart.el --- Encourage when you programming -*- lexical-binding: t; -*-
3 ;;; Time-stamp: <2020-06-26 08:49:32 stardiviner>
5 ;; Authors: stardiviner <numbchild@gmail.com>
6 ;; Package-Requires: ((emacs "25.1") (flycheck "32-cvs"))
7 ;; Package-Version: 0.1
8 ;; Keywords: tools
9 ;; homepage: https://github.com/stardiviner/emacs-rainbow-fart
11 ;; rainbow-fart is free software; you can redistribute it and/or modify it
12 ;; under the terms of the GNU General Public License as published by
13 ;; the Free Software Foundation; either version 3, or (at your option)
14 ;; any later version.
16 ;; rainbow-fart is distributed in the hope that it will be useful, but WITHOUT
17 ;; ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
18 ;; or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
19 ;; License for more details.
22 ;; You should have received a copy of the GNU General Public License
23 ;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>.
25 ;;; Commentary:
26 ;;
27 ;; Usage:
28 ;; (add-hook 'prog-mode-hook #'rainbow-fart-mode)
30 ;;; Code:
32 (require 'flycheck)
33 (require 'url)
35 (defgroup rainbow-fart nil
36 "rainbow-fart-mode customize group."
37 :prefix "rainbow-fart-"
38 :group 'rainbow-fart)
40 (defcustom rainbow-fart-voice-alist
41 '(("defun" . ("function.mp3" "function_01.mp3" "function_02.mp3" "function_03.mp3"))
42 ("defn" . ("function.mp3" "function_01.mp3" "function_02.mp3" "function_03.mp3"))
43 ("def" . ("function.mp3" "function_01.mp3" "function_02.mp3" "function_03.mp3"))
44 ("fn" . ("function.mp3" "function_01.mp3" "function_02.mp3" "function_03.mp3"))
45 ("lambda" . ("function.mp3" "function_01.mp3" "function_02.mp3" "function_03.mp3"))
46 ("function" . ("function.mp3" "function_01.mp3" "function_02.mp3" "function_03.mp3"))
47 ("->" . ("arrow_function_01.mp3"))
48 ("->>" . ("arrow_function_01.mp3"))
49 ("=>" . ("arrow_function_01.mp3"))
50 ("if" . ("if_01.mp3" "if_02.mp3" "if_03.mp3"))
51 ("while" . ("if_01.mp3" "if_02.mp3" "if_03.mp3"))
52 ("when" . ("if_01.mp3" "if_02.mp3" "if_03.mp3"))
53 ("until" . ("if_01.mp3" "if_02.mp3" "if_03.mp3"))
54 ("for" . ("for_01.mp3" "for_02.mp3" "for_03.mp3"))
55 ("loop" . ("for_01.mp3" "for_02.mp3" "for_03.mp3"))
56 ("await" . ("await_01.mp3" "await_02.mp3" "await_03.mp3"))
57 ("promise" . ("await_01.mp3" "await_02.mp3" "await_03.mp3"))
58 ("catch" . ("catch_01.mp3" "catch_02.mp3" "catch_03.mp3"))
59 ("import" . ("import_01.mp3" "import_02.mp3"))
60 (":import" . ("import_01.mp3" "import_02.mp3"))
61 (":require" . ("import_01.mp3" "import_02.mp3"))
62 ("require" . ("import_01.mp3" "import_02.mp3"))
63 ("load" . ("import_01.mp3" "import_02.mp3"))
64 ("load-file" . ("import_01.mp3" "import_02.mp3"))
65 ("v-html" . ("v_html_01.mp3"))
66 ("fuck" . ("fuck_pm_01.mp3" "fuck_pm_02.mp3"))
67 ("shit" . ("fuck_pm_01.mp3" "fuck_pm_02.mp3"))
68 ("damn" . ("fuck_pm_01.mp3" "fuck_pm_02.mp3"))
69 ;; time
70 ("hour" . ("time_each_hour_01.mp3" "time_each_hour_02.mp3"
71 "time_each_hour_03.mp3" "time_each_hour_04.mp3" "time_each_hour_05.mp3"))
72 ("morning" . ("time_morning_01.mp3"))
73 ("before_noon" . ("time_before_noon_01.mp3" "time_before_noon_02.mp3"
74 "time_before_noon_03.mp3" "time_before_noon_04.mp3"))
75 ("noon" . ("time_noon_01.mp3"))
76 ("evening" . ("time_evening_01.mp3"))
77 ("midnight" . ("time_midnight_01.mp3"))
78 ;; TODO `flycheck' support
79 ("info" . ())
80 ("warning" . ())
81 ("error" . ()))
82 "An alist of pairs of programming language keywords and voice filenames."
83 :type 'alist
84 :safe #'listp
85 :group 'rainbow-fart)
87 (defcustom rainbow-fart-voice-model "JustKowalski"
88 "The voice model to be used."
89 :type 'string
90 :safe #'stringp
91 :group 'rainbow-fart)
93 (defcustom rainbow-fart-voices-directory
94 (concat (file-name-directory (or load-file-name buffer-file-name)) "voices/")
95 "The directory of voices."
96 :type 'string
97 :safe #'stringp
98 :group 'rainbow-fart)
100 (defcustom rainbow-fart-keyword-interval (* 60 5)
101 "The time interval in seconds of rainbow-fart play voice for keywords.
102 If it is nil, will play sound for every keywords."
103 :type 'number
104 :safe #'numberp
105 :group 'rainbow-fart)
107 (defcustom rainbow-fart-time-interval (* 60 15)
108 "The time interval in seconds of rainbow-fart play voice for hours.
109 If it's nil, the hours remind will not started."
110 :type 'number
111 :safe #'numberp
112 :group 'rainbow-fart)
114 (defvar rainbow-fart--playing nil
115 "The status of rainbow-fart playing.")
117 (defvar rainbow-fart--play-last-time nil
118 "The last time of rainbow-fart play.")
120 (defun rainbow-fart--get-media-uri (keyword)
121 "Get media uri based on KEYWORD."
122 (when-let ((uris (cdr (assoc keyword rainbow-fart-voice-alist))))
123 (let ((uri (nth (random (length uris)) uris))
124 (voice-model-directory
125 (expand-file-name rainbow-fart-voice-model rainbow-fart-voices-directory)))
126 (if (url-type (url-generic-parse-url uri))
128 (let ((uri (expand-file-name uri voice-model-directory)))
129 (when (file-exists-p uri)
130 uri))))))
133 (defun rainbow-fart--play (keyword)
134 "A private function to play voice for matched KEYWORD."
135 (unless (or rainbow-fart--playing
136 (when rainbow-fart-keyword-interval
137 (not (if rainbow-fart--play-last-time
138 (> (- (float-time) rainbow-fart--play-last-time) rainbow-fart-keyword-interval)
139 (setq rainbow-fart--play-last-time (float-time))))))
140 (when-let ((uri (rainbow-fart--get-media-uri keyword))
141 (command (or
142 (executable-find "mpg123")
143 (executable-find "mplayer")
144 (executable-find "mpv"))))
145 (setq rainbow-fart--playing t)
146 (make-process :name "rainbow-fart"
147 :command `(,command ,uri)
148 :buffer "*rainbow-fart*"
149 :sentinel (lambda (_ __)
150 (setq rainbow-fart--playing nil)
151 (setq rainbow-fart--play-last-time (float-time)))))))
152 ;;; prefix detection
154 (defun rainbow-fart--post-self-insert ()
155 "A hook function on `post-self-insert-hook' to play audio."
156 (let* ((prefix (thing-at-point 'symbol))
157 (face (get-text-property (1- (point)) 'face)))
158 (when (or (memq face '(font-lock-keyword-face))
159 (null face))
160 (rainbow-fart--play prefix))))
162 ;;; linter like `flycheck'
164 (defun rainbow-fart--linter-display-error (err)
165 "Play voice for `flycheck-error' ERR."
166 (let ((level (flycheck-error-level err)))
167 (rainbow-fart--play level)))
169 (defun rainbow-fart--linter-display-errors (errors)
170 "A function to report ERRORS used as replacement of linter like `flycheck' and `flymake'."
171 (rainbow-fart--play
172 (mapc #'rainbow-fart--linter-display-error
173 (seq-uniq
174 (seq-mapcat #'flycheck-related-errors errors)))))
176 ;;; timer
178 (defun rainbow-fart--timing ()
179 "Play voice for current time quantum."
180 (let* ((time (format-time-string "%H:%M"))
181 (pair (split-string time ":"))
182 (hour (string-to-number (car pair))))
183 (cond
184 ((and (> hour 05) (< hour 08)) ; 05:00 -- 08:00
185 "morning")
186 ((and (> hour 08) (< hour 10)) ; 08:00 -- 10:00
187 "hour")
188 ((and (> hour 10) (< hour 11)) ; 10:00 -- 11:00
189 "before_noon")
190 ((and (> hour 11) (< hour 13)) ; 11:00 -- 13:00
191 "noon")
192 ((and (> hour 13) (< hour 15)) ; 13:00 -- 15:00
193 "hour")
194 ((and (> hour 15) (< hour 17)) ; 15:00 -- 17:00
195 "afternoon")
196 ((and (> hour 18) (< hour 22)) ; 18:00 -- 21:00
197 "evening")
198 ((or (> hour 23) (< hour 01)) ; 23:00 -- 01:00
199 "midnight"))))
201 (defun rainbow-fart--timing-remind ()
202 "Remind you in specific time quantum."
203 (when (and rainbow-fart--play-last-time
204 (> (- (float-time) rainbow-fart--play-last-time) rainbow-fart-time-interval))
205 (rainbow-fart--play (rainbow-fart--timing))
206 (setq rainbow-fart--play-last-time (float-time))))
208 (defvar rainbow-fart--timer nil)
210 ;;;###autoload
211 (define-minor-mode rainbow-fart-mode
212 "A minor mode add an encourager when you programming."
213 :init-value nil
214 :lighter " rainbow-fart "
215 :group 'rainbow-fart
216 (if rainbow-fart-mode
217 (progn
218 (add-hook 'post-self-insert-hook #'rainbow-fart--post-self-insert t t)
219 (advice-add (buffer-local-value 'flycheck-display-errors-function (current-buffer))
220 :before 'rainbow-fart--linter-display-errors)
221 (when rainbow-fart-time-interval
222 (setq rainbow-fart--timer
223 (run-with-timer 10 rainbow-fart-time-interval 'rainbow-fart--timing-remind))))
224 (remove-hook 'post-self-insert-hook #'rainbow-fart--post-self-insert t)
225 (advice-remove (buffer-local-value 'flycheck-display-errors-function (current-buffer))
226 'rainbow-fart--linter-display-errors)
227 (when rainbow-fart--timer
228 (cancel-timer rainbow-fart--timer))))
232 (provide 'rainbow-fart)
234 ;;; rainbow-fart.el ends here