Controlling my stream audio from Emacs: background music, typing sounds, and push to talk
Posted: - Modified: | emacs Update: 2021-02-11: Parsed pacmd list-sources
so that I can mute/unmute devices by regular expression. Update: 2021-02-07: Made it work with my USB microphone.
I was experimenting with streaming Emacs geeking around on twitch.tv. Someone asked me to have soft background music and typing sounds. Since I'm a little clueless about music and don't want to bother with hunting down nice royalty-free music, I figured I could just use the Mozart dice game to programmatically generate music.
I installed the mozart-dice-game NPM package and used this bit of Javascript to generate a hundred MIDI files.
const x = require('mozart-dice-game') for (let i = 0; i < 100; i++) { x.saveMinuet('minuet' + String(i).padStart('3', '0') + '.mid'); }
Then I wrote this Emacs Lisp function to turn it on and off.
(defvar my/background-music-process nil "Process for playing background music") (defun my/stream-toggle-background-music (&optional enable) (interactive) (if (or my/background-music-process (and (numberp enable) (< enable 0))) (progn (when (process-live-p my/background-music-process) (kill-process my/background-music-process)) (setq my/background-music-process nil)) (let ((files (directory-files "~/code/music" t "mid\\'"))) (setq my/background-music-process (apply 'start-process "*Music*" nil (append (list "timidity" "-idlr" "--volume=10") files))))))
People also suggested typing sounds. I guess that's a good way to get a sense of activity. The default selectric sound was a little too loud for me, so we'll use the move sound for now. It would be nice to make this more random-sounding someday.
(defun my/selectric-type-sound () "Make the sound of typing." ;; Someday, randomize this or something (selectric-make-sound (expand-file-name "selectric-move.wav" selectric-files-path))) (use-package selectric-mode :diminish "" :config (fset #'selectric-type-sound #'my/selectric-type-sound))
I was having a hard time remembering to go back on mute during meetings, since the LED on the mute button wasn't working at the time and the system tray icon was a little hard to notice. The LED has mysteriously decided to start working again, but push-to-talk is handy anyway. I want to be able to tap a key to toggle my microphone on and off, and hold it down in order to make it push-to-talk. It looks like my key repeat is less than 0.5 seconds, so I can set a timer that will turn things off after a little while. This code doesn't pick up any changes that happen outside Emacs, but it'll do for now. I used pacmd list-sources
to list the sources and get the IDs.
(defun my/pacmd-set-device (regexp status) (with-current-buffer (get-buffer-create "*pacmd*") (erase-buffer) (shell-command "pacmd list-sources" (current-buffer)) (goto-char (point-max)) (let (results) (while (re-search-backward regexp nil t) (when (re-search-backward "index: \\([[:digit:]]+\\)" nil t) (setq results (cons (match-string 1) results)) (shell-command-to-string (format "pacmd set-source-mute %s %d" (match-string 1) (if (equal status 'on) 0 1))))) results))) (defvar my/mic-p nil "Non-nil means microphone is on") (add-to-list 'mode-line-front-space '(:eval (if my/mic-p "*MIC*" ""))) (defun my/mic-off () (interactive) (my/pacmd-set-device "Yeti" 'off) (my/pacmd-set-device "Internal Microphone" 'off) (setq my/mic-p nil)) (defun my/mic-on () (interactive) (my/pacmd-set-device "Yeti" 'on) (my/pacmd-set-device "Internal Microphone" 'on) (setq my/mic-p t)) (defun my/mic-toggle () (interactive) (if my/mic-p (my/mic-off) (my/mic-on))) (defvar my/push-to-talk-mute-timer nil "Timer to mute things again.") (defvar my/push-to-talk-last-time nil "Last time my/push-to-talk was run") (defvar my/push-to-talk-threshold 0.5 "Number of seconds") (defun my/push-to-talk-mute () (interactive) (message "Muting.") (my/mic-off) (force-mode-line-update) (my/obs-websocket-add-subtitle (my/obs-websocket-stream-time-msecs) "[Microphone off]")) (defun my/push-to-talk () "Tap to toggle microphone on and off, or repeat the command to make it push to talk." (interactive) (cond ((null my/mic-p) ;; It's off, so turn it on (when (timerp my/push-to-talk-mute-timer) (cancel-timer my/push-to-talk-mute-timer)) (my/mic-on) (my/obs-websocket-add-subtitle (my/obs-websocket-stream-time-msecs) "[Microphone on]") (setq my/push-to-talk-last-time (current-time))) ((timerp my/push-to-talk-mute-timer) ;; Push-to-talk mode (cancel-timer my/push-to-talk-mute-timer) (setq my/push-to-talk-mute-timer (run-at-time my/push-to-talk-threshold nil #'my/push-to-talk-mute))) ;; Might be push to talk, if we're within the key repeating time ((< (- (time-to-seconds (current-time)) (time-to-seconds my/push-to-talk-last-time)) my/push-to-talk-threshold) (setq my/push-to-talk-mute-timer (run-at-time my/push-to-talk-threshold nil #'my/push-to-talk-mute))) ;; It's been a while since I turned the mic on. (t (my/push-to-talk-mute)))) (global-set-key (kbd "<f12>") #'my/push-to-talk)