Getting Emacs to read to me

| categories: emacs | tags:

I thought it would be interesting to have Emacs read text on the screen. Why? Sometimes I get tired of reading ;) Seriously though, this has applications in accessibility, learning to read, translation, taking a break from looking at the screen, reading emails out loud, fun and games, etc… Seems like a worthwhile endeavor!

You may want to see this video: https://www.youtube.com/watch?v=8bgS8yDSkXw to hear how it works.

On a Mac, it turns out to be easy to get a voice with a little applescript:

(do-applescript "say \"Hello John\" using \"Victoria\"")

Interesting idea to integrate some feedback into Emacs-lisp functions! at least if you are on a Mac. All we need are some interactive functions that grab text, and pass them to the applescript with an appropriate amount of escaping any quotes and backslashes.

Here is a function to speak the word at point, or selected region, or the text passed to the function:

(defvar words-voice "Vicki"
  "Mac voice to use for speaking.")

(defun words-speak (&optional text)
  "Speak word at point or region. Mac only."
  (interactive)
  (unless text
    (setq text (if (use-region-p)
                   (buffer-substring
                    (region-beginning) (region-end))
                 (thing-at-point 'word))))
  ;; escape some special applescript chars
  (setq text (replace-regexp-in-string "\\\\" "\\\\\\\\" text))
  (setq text (replace-regexp-in-string "\"" "\\\\\"" text))
  (do-applescript
   (format
    "say \"%s\" using \"%s\""
    text
    words-voice)))
words-speak

Now we can write:

(words-speak "Hello John")

One reason I wrote this is to read org-files to me. So, now we write some functions to read words, sentences and paragraphs. These are all syntactic units in Emacs. We write code to enable us to read the next or previous units with the prefix args. Finally, we bind the commands to some keys and a hydra for fun.

(setq sentence-end-double-space nil)

(defun mac-say-word (&optional arg)
  "Speak word at point. With ARG, go forward ARG words."
  (interactive "P")
  ;; arg can be (4), 4, "-", or -1. we handle these like this.
  (let ((newarg))
    (when arg
      (setq newarg (cond
                    ((listp arg)
                     (round (log (car arg) 4)))
                    ((and (stringp arg) (string= "-" arg))
                     ((< 0 arg) arg)
                     -1)
                    (t arg)))
      (forward-word newarg))
    (when (thing-at-point 'word)
      (words-speak (thing-at-point 'word)))))

(defun mac-say-sentence (&optional arg)
  "Speak sentence at point. With ARG, go forward ARG sentences."
  (interactive "P")
  ;; arg can be (4), 4, "-", or -1. we handle these like this.
  (let ((newarg))
    (when arg
      (setq newarg (cond
                    ((listp arg)
                     (round (log (car arg) 4)))
                    ((and (stringp arg) (string= "-" arg))
                     ((< 0 arg) arg)
                     -1)
                    (t arg)))
      (forward-sentence newarg)
      (when (< 0 newarg) (forward-word)))
    (when (thing-at-point 'sentence)
      (words-speak (thing-at-point 'sentence)))))

(defun mac-say-paragraph (&optional arg)
  "Speak paragraph at point. With ARG, go forward ARG paragraphs."
  (interactive "P")
  ;; arg can be (4), 4, "-", or -1. we handle these like this.
  (let ((newarg))
    (when arg
      (setq newarg (cond
                    ((listp arg)
                     (round (log (car arg) 4)))
                    ((and (stringp arg) (string= "-" arg))
                     ((< 0 arg) arg)
                     -1)
                    (t arg)))
      (forward-paragraph newarg)
      (when (< 0 newarg) (forward-word)))
    (when (thing-at-point 'paragraph)
      (words-speak (thing-at-point 'paragraph)))))
mac-say-paragraph

Now for some key-bindings. I will make a hydra that allows repeating commands, and a keymap for more direct function calls.

(defhydra mac-speak (:color red)
  "word speak"
  ("w" (progn (mac-say-word) (forward-word)) "Next word")
  ("W" (mac-say-word -1) "Previous word")
  ("s" (progn (mac-say-sentence) (forward-sentence)(forward-word)) "Next sentence")
  ("S" (mac-say-sentence -1) "Previous sentence")
  ("p" (progn (mac-say-paragraph) (forward-paragraph)) "Next paragraph")
  ("P" (mac-say-paragraph -1) "Previous paragraph"))

(define-prefix-command 'mac-speak-keymap)
(define-key mac-speak-keymap (vector ?w) 'mac-say-word)
(define-key mac-speak-keymap (vector ?s) 'mac-say-sentence)
(define-key mac-speak-keymap (vector ?p) 'mac-say-paragraph)
(define-key mac-speak-keymap (vector ?h) 'mac-speak/body)
(global-set-key (kbd "\C-xr") 'mac-speak-keymap)
mac-speak-keymap

Now, I can navigate text and have my Mac read it to me. It isn't quite like hearing a real person read it, but it is not too bad either. When you need a break from reading, this might be a nice tool!

Copyright (C) 2015 by John Kitchin. See the License for information about copying.

org-mode source

Org-mode version = 8.2.10

Discuss on Twitter