;;; hsc3.el --- Haskell SuperCollider ;;; Commentary: ; Indentation and font locking is courtesy `haskell' mode (debian=haskell-mode). ; Inter-process communication is courtesy `comint'. ; Symbol at point acquisition is courtesy `thingatpt'. ; Directory search facilities are courtesy `find-lisp'. (require 'haskell) (require 'comint) (require 'thingatpt) (require 'find-lisp) ;;; Code: (defcustom hsc3-buffer "*hsc3*" "The name of the hsc3 haskell process buffer." :type 'string) (defcustom hsc3-interpreter (list "ghci") "The name of the haskell interpreter (default=\"ghci\")." :type '(repeat string)) (defvar hsc3-directory nil "The hsc3 directory (default=nil).") (defvar sc3-help-directory nil "The directory containing the Sc3 RTF help files (default=nil). These can be fetched from the SuperCollider archives by typing: git checkout 2495f4f61c0955001169d28142543aabbe1bedcb build/old_rtf_help.tar.gz") (defcustom hsc3-auto-import-modules nil "Flag to decide if the hsc3 standard module set is imported on startup (default=nil)." :type 'boolean) (defun hsc3-chunk-string (n s) "Split the string S into chunks of N characters." (let* ((l (length s)) (m (min l n)) (c (substring s 0 m))) (if (<= l n) (list c) (cons c (hsc3-chunk-string n (substring s n)))))) (defun hsc3-send-line (s) "Send the string S, with newline appended, to haskell." (if (comint-check-proc hsc3-buffer) (let ((cs (hsc3-chunk-string 64 (concat s "\n")))) (mapcar (lambda (c) (comint-send-string hsc3-buffer c)) cs)) (error "No hsc3 process?"))) (defun hsc3-send-layout-block (s) "Send the string S to haskell using ghci layout block notation." (hsc3-send-line (mapconcat 'identity (list ":{" s ":}") "\n"))) (defun hsc3-send-text (str) "If the string STR spans multiple lines `hsc3-send-layout-block' else `hsc3-send-line'." (if (string-match "\n" str) (hsc3-send-layout-block str) (hsc3-send-line str))) (defun hsc3-send-text-fn (fn str) "Send string STR with haskell function FN applied using the $ operator. If STR has a newline the layout is adjusted accordingly." (hsc3-send-text (if (string-match "\n" str) (concat fn " $\n" str) (concat fn " $ " str)))) (defun hsc3-send-quit () "Send :quit instruction to haskell." (interactive) (hsc3-send-line ":quit")) (defun hsc3-uncomment (s) "Remove initial comment and Bird-literate markers from the string S if present." (replace-regexp-in-string "^[- ]*[> ]*" "" s)) (defun hsc3-find-files (dir rgx) "Find files at DIR matching RGX." (mapc (lambda (filename) (find-file-other-window filename)) (find-lisp-find-files dir rgx))) (defun hsc3-help () "Lookup up the name at point in the hsc3 help files." (interactive) (let ((rgx (concat "^" (thing-at-point 'symbol) "\\.help\\.l?hs$"))) (hsc3-find-files (concat hsc3-directory "Help/") rgx))) (defun hsc3-sc3-help () "Lookup up the name at point in the Sc3 (RTF) help files." (interactive) (let ((rgx (concat "^" (thing-at-point 'symbol) "\\\(.help\\\)?.rtf$"))) (hsc3-find-files sc3-help-directory rgx))) (defun hsc3-sc3-help-scdoc () "Lookup up the Ugen name at point in the Sc3 (SCDOC) help files." (interactive) (hsc3-send-line (format "Sound.Sc3.Common.Help.sc3_scdoc_help_open False (Sound.Sc3.Common.Help.sc3_scdoc_help_path (Sound.Sc3.Ugen.Db.ugen_sc3_name \"%s\"))" (thing-at-point 'symbol)))) (defun hsc3-ugen-summary () "Lookup up the Ugen at point in hsc3-db." (interactive) (hsc3-send-line (format "Sound.Sc3.Ugen.Db.Pp.ugen_summary_wr \"%s\"" (thing-at-point 'symbol)))) (defun hsc3-ugen-default-param () "Insert the default Ugen parameters (arguments) for the Ugen before ." (interactive) (let ((p (format "hsc3-help ugen exemplar hs %s" (thing-at-point 'symbol)))) (insert " ") (insert (hsc3-remove-trailing-newline (shell-command-to-string p))))) (defun hsc3-ugen-control-param () "Insert control Ugen parameters (arguments) for the Ugen before ." (interactive) (let ((p (format "hsc3-help ugen exemplar hs/control %s" (thing-at-point 'symbol)))) (insert (hsc3-remove-trailing-newline (shell-command-to-string p))))) (defun hsc3-ugen-control-param-let () "Insert control Ugen parameters (arguments) for the Ugen before ." (interactive) (let ((p (format "hsc3-help ugen exemplar hs/control/let %s" (thing-at-point 'symbol)))) (insert (hsc3-remove-trailing-newline (shell-command-to-string p))))) (defun hsc3-remove-trailing-newline (str) "Delete trailing newlines from STR." (replace-regexp-in-string "\n\\'" "" str)) (defun hsc3-cd () "Change directory at ghci to current value of 'default-directory'." (interactive) (hsc3-send-line (format ":cd %s" default-directory))) (defun hsc3-load-current-file () "Send :load and the current buffer file name to haskell." (interactive) (save-buffer) (hsc3-see-haskell) (hsc3-send-line (format ":load \"%s\"" buffer-file-name))) (defun hsc3-current-line () "Get current line." (buffer-substring-no-properties (line-beginning-position) (line-end-position))) (defun hsc3-send-current-line () "Send the current line to haskell." (interactive) (hsc3-send-line (hsc3-uncomment (hsc3-current-line)))) (defun hsc3-send-main () "Send main to haskell." (interactive) (hsc3-send-line "main")) (defun hsc3-set-region-to-paragraph () "Set the mark at the start and point at the end of the current paragraph." (interactive) (backward-paragraph) (push-mark nil t t) (forward-paragraph)) (defun hsc3-region-string () "Get current region as string." (buffer-substring-no-properties (region-beginning) (region-end))) (defun hsc3-send-region () "Send region text to interpreter." (interactive) (hsc3-send-text (hsc3-region-string))) (defun hsc3-send-region-fn (fn) "Send region text with haskell function FN to be applied." (hsc3-send-text-fn fn (hsc3-region-string))) (defun hsc3-play-region-at (k) "Play region at scsynth. The (one-indexed) prefix agument K indicates which server to send to." (interactive "p") (hsc3-send-region-fn (format "Sound.Sc3.auditionAt (\"%s\",%d + %d) Sound.Sc3.def_play_opt" (hsc3-server-host) (hsc3-server-port) k))) (defun hsc3-play-region () "Play region at scsynth." (interactive) (hsc3-send-region-fn "Sound.Sc3.audition")) (defun hsc3-play () "Play at scsynth." (interactive) (hsc3-set-region-to-paragraph) (hsc3-play-region)) (defun hsc3-play-region-stateful () "Play region at scsynth using stateful interface." (interactive) (hsc3-send-region-fn "Sound.Sc3.scSynthPlay scSynth")) (defcustom hsc3-draw-command "draw" "*The un-qualified name of the draw function to use at `hsc3-draw-region'." :type 'string) (defun hsc3-draw-region () "Draw region Ugen graph." (interactive) (hsc3-send-region-fn (format "Sound.Sc3.Ugen.Dot.%s $ wrapOut Nothing " hsc3-draw-command))) (defun hsc3-draw () "Draw Ugen graph." (interactive) (hsc3-set-region-to-paragraph) (hsc3-draw-region)) (defun hsc3-dump-ugens-region () "Print region Ugen graph." (interactive) (hsc3-send-region-fn "Sound.Sc3.ugen_dump_ugens $ wrapOut Nothing")) (defun hsc3-ui-region () "UI for region Ugen graph." (interactive) (let ((str (hsc3-region-string))) (hsc3-send-region-fn "Sound.Sc3.UI.SCLang.Control.ugen_ui_run \"ui\" 1"))) (defun hsc3-ui-comment () "UI for description in comment line." (interactive) (hsc3-send-line (format "Sound.Sc3.UI.SCLang.Control.control_text_ui_run \"ui\" 1 \"%s\"" (hsc3-current-line)))) (defun hsc3-pp-html-region () "HTML pretty-printer and viewer for region Ugen graph." (interactive) (let ((str (hsc3-region-string))) (hsc3-send-region-fn "Sound.Sc3.UI.HTML.ugen_graph_pp_html_wv"))) (defun hsc3-pp-forth () "Forth PP." (interactive) (hsc3-send-region-fn "Sound.Sc3.Ugen.Db.Pp.ugen_graph_forth_pp (False,False)")) (defun hsc3-pp-smalltalk () "Pretty print Ugen as Smalltalk." (interactive) (hsc3-send-region-fn "Sound.Sc3.Ugen.Db.Pp.Graph.ugen_graph_smalltalk_pp")) (defun hsc3-id-rewrite-region () "Run hsc3-id-rewrite on region." (interactive) (shell-command-on-region (region-beginning) (region-end) "hsc3-rw id-rewrite" nil t)) (defun hsc3-id-rewrite-buffer () "Run hsc3-id-rewrite on buffer." (interactive) (shell-command-on-region (point-min) (point-max) "hsc3-rw id-rewrite" nil t)) (defun hsc3-uparam-rewrite () "Rewrite uparam notation inplace." (interactive) (shell-command-on-region (region-beginning) (region-end) "hsc3-rw uparam expand" nil t)) (defun hsc3-server-protocol () "The protocol that scsynth is listening at." (or (getenv "ScProtocol") "Udp")) (defun hsc3-server-host () "The host that scsynth is listening at." (or (getenv "ScHostname") "127.0.0.1")) (defun hsc3-server-port () "The port that scsynth is listening at." (read (or (getenv "ScPort") "57110"))) (defun hsc3-with-sc3 (txt) "Sound.Sc3.withSc3 at `hsc3-server-host' and `hsc3-server-port' of the string TXT." (hsc3-send-line (format "Sound.Sc3.withSc3At (%s,\"%s\",%d) %s" (hsc3-server-protocol) (hsc3-server-host) (hsc3-server-port) txt))) (defun hsc3-reset-scsynth () "Send Sc3 reset instruction to haskell." (interactive) (hsc3-with-sc3 "Sound.Sc3.reset")) ;;(hsc3-send-line "Sound.Sc3.scsynthReset scsynth") (defun hsc3-start-haskell () "Start the hsc3 haskell process. If `hsc3-interpreter' is not already a subprocess it is started and a new window is created to display the results of evaluating hsc3 expressions. Input and output is via `hsc3-buffer'." (interactive) (if (comint-check-proc hsc3-buffer) (hsc3-see-haskell) (apply 'make-comint "hsc3" (car hsc3-interpreter) nil (cdr hsc3-interpreter)) (hsc3-set-prompt) (if hsc3-auto-import-modules (hsc3-import-standard-modules)) (hsc3-see-haskell))) (defun hsc3-interrupt-haskell () "Interupt haskell." (interactive) (interrupt-process hsc3-buffer comint-ptyp)) (defun hsc3-stop () "Interrupt haskell & reset scsynth." (interactive) (progn (hsc3-interrupt-haskell) (sleep-for 0.15) (hsc3-reset-scsynth))) (defun hsc3-server-status () "Send serverStatus request to haskell." (interactive) (hsc3-with-sc3 "Sound.Sc3.serverStatus >>= mapM putStrLn")) (defun hsc3-quit-scsynth () "Quit." (interactive) (hsc3-with-sc3 "(Sound.Osc.sendMessage Sound.Sc3.quit)")) (defcustom hsc3-seq-degree 2 "Number of scsynth processes to address at -seq operations (default=2)." :type 'integer) (defun hsc3-server-status-seq () "Send serverStatus request to haskell." (interactive) (hsc3-send-line (format "Sound.Sc3.withSc3AtSeq (\"%s\",%d) %d Sound.Sc3.serverStatus >>= mapM putStrLn . concat" (hsc3-server-host) (hsc3-server-port) hsc3-seq-degree))) (defun hsc3-play-region-seq () "Sound.Sc3.auditionAtSeq of `hsc3-seq-degree'." (interactive) (hsc3-send-region-fn (format "Sound.Sc3.auditionAtSeq (\"%s\",%d) def_play_opt %d" (hsc3-server-host) (hsc3-server-port) hsc3-seq-degree))) (defun hsc3-reset-scsynth-seq () "Send Sc3 reset instruction to haskell." (interactive) (hsc3-send-line (format "Sound.Sc3.withSc3AtSeq_ (\"%s\",%d) %d Sound.Sc3.reset" (hsc3-server-host) (hsc3-server-port) hsc3-seq-degree))) (defun hsc3-dmenu-ugen-core () "Run dmenu to select a core Sc3 Ugen." (interactive) (insert (shell-command-to-string "hsc3-db dmenu ugen core"))) (defun hsc3-dmenu-ugen-ext () "Run dmenu to select an external Sc3 Ugen." (interactive) (insert (shell-command-to-string "hsc3-db dmenu ugen external"))) (defun hsc3-dmenu-ugen-all () "Run dmenu to select an Sc3 Ugen." (interactive) (insert (shell-command-to-string "hsc3-db dmenu ugen all"))) (defun hsc3-xmenu-ugen-core () "Run xmenu to select a core Sc3 Ugen." (interactive) (insert (shell-command-to-string "hsc3-db xmenu core"))) (defun hsc3-xmenu-ugen-ext () "Run xmenu to select an external Sc3 Ugen." (interactive) (insert (shell-command-to-string "hsc3-db xmenu external"))) (defun hsc3-xmenu-ugen-all () "Run xmenu to select an Sc3 Ugen." (interactive) (insert (shell-command-to-string "cat ~/sw/hsc3-db/lib/xmenu/ugen-core-tree.text \ ~/sw/hsc3-db/lib/xmenu/nil.text \ ~/sw/hsc3-db/lib/xmenu/ugen-ext-tree.text | xmenu"))) (defun hsc3-load-file (fn) "Load file FN as string." (with-temp-buffer (insert-file-contents fn) (buffer-substring-no-properties (point-min) (point-max)))) (defun hsc3-unload-all-modules () "Unload all modules and delete all bindings at ghci. :load without any arguments unloads all the currently loaded modules and bindings" (interactive) (hsc3-send-line ":load")) (defun hsc3-import-standard-modules () "Send standard set of hsc3 and related module imports to haskell." (interactive) (mapc 'hsc3-send-line (split-string (hsc3-load-file (concat hsc3-directory "lib/hsc3-std-imports.hs")) "\n"))) (defun hsc3-init-scsynth () "Initialise the scsynth interpreter variable." (interactive) (hsc3-send-line "scSynth <- Sound.Sc3.Server.ScSynth.newScSynth")) (defun hsc3-set-prompt () "Set ghci prompt to hsc3> and the continuation prompt to nil." (interactive) (hsc3-send-line ":set prompt \"hsc3> \"") (hsc3-send-line ":set prompt-cont \"\"")) (defun hsc3-see-haskell () "Show haskell output." (interactive) (if (not (comint-check-proc hsc3-buffer)) (hsc3-start-haskell) (delete-other-windows) (split-window-vertically) (with-current-buffer hsc3-buffer (let ((window (display-buffer (current-buffer)))) (goto-char (point-max)) (save-selected-window (set-window-point window (point-max))))))) (defvar hsc3-mode-map nil "Haskell SuperCollider keymap.") (defun hsc3-mode-keybindings (map) "Add Haskell SuperCollider keybindings to MAP." (define-key map (kbd "C-c <") 'hsc3-load-current-file) (define-key map (kbd "C-c >") 'hsc3-see-haskell) (define-key map (kbd "C-c C-c") 'hsc3-send-current-line) (define-key map (kbd "C-c C-h") 'hsc3-help) (define-key map (kbd "C-c C-r") 'hsc3-send-region) (define-key map (kbd "C-c C-a") 'hsc3-play) (define-key map (kbd "C-c C-S-a") 'hsc3-play-region-seq) (define-key map (kbd "C-c C-g") 'hsc3-draw) (define-key map (kbd "C-c C-d") 'hsc3-dump-ugens-region) (define-key map (kbd "C-c C-v") 'hsc3-ui-region) (define-key map (kbd "C-c C-f") 'hsc3-ui-comment) (define-key map (kbd "C-c C-j") 'hsc3-sc3-help) (define-key map (kbd "C-c C-i") 'hsc3-interrupt-haskell) (define-key map (kbd "C-c C-k") 'hsc3-reset-scsynth) (define-key map (kbd "C-c C-S-k") 'hsc3-reset-scsynth-seq) (define-key map (kbd "C-c C-m") 'hsc3-send-main) (define-key map (kbd "C-c C-p") 'hsc3-server-status) (define-key map (kbd "C-c C-S-p") 'hsc3-server-status-seq) (define-key map (kbd "C-c C-q") 'hsc3-send-quit) (define-key map (kbd "C-c C-.") 'hsc3-stop) (define-key map (kbd "C-c C-s") 'hsc3-dmenu-ugen-all) (define-key map (kbd "C-c C-S-s") 'hsc3-xmenu-ugen-all) (define-key map (kbd "C-c C-u") 'hsc3-ugen-summary)) (defun hsc3-mode-menu (map) "Add Haskell SuperCollider Menu to MAP." (define-key map [menu-bar hsc3] (cons "Haskell-SuperCollider" (make-sparse-keymap "Haskell-SuperCollider"))) (define-key map [menu-bar hsc3 help] (cons "Help" (make-sparse-keymap "Help"))) (define-key map [menu-bar hsc3 help hsc3] '("HSc3 Help" . hsc3-help)) (define-key map [menu-bar hsc3 help ugen] '("Ugen Summary" . hsc3-ugen-summary)) (define-key map [menu-bar hsc3 help sc3-ugen] '("Sc3 Help" . hsc3-sc3-ugen-help)) (define-key map [menu-bar hsc3 expression] (cons "Expression" (make-sparse-keymap "Expression"))) (define-key map [menu-bar hsc3 expression stop] '("Stop (interrupt and reset)" . hsc3-stop)) (define-key map [menu-bar hsc3 expression change-directory] '("Change directory" . hsc3-cd)) (define-key map [menu-bar hsc3 expression import-standard-modules] '("Import standard modules" . hsc3-import-standard-modules)) (define-key map [menu-bar hsc3 expression server-status] '("Print server status" . hsc3-server-status)) (define-key map [menu-bar hsc3 expression send-main] '("Send main" . hsc3-send-main)) (define-key map [menu-bar hsc3 expression send-region] '("Send region" . hsc3-send-region)) (define-key map [menu-bar hsc3 expression send-current-line] '("Send current line" . hsc3-send-current-line)) (define-key map [menu-bar hsc3 expression id-rewrite-region] '("ID-rewrite region" . hsc3-id-rewrite-region)) (define-key map [menu-bar hsc3 expression load-current-file] '("Load current file" . hsc3-load-current-file)) (define-key map [menu-bar hsc3 expression dmenu-ugen] '("Ugen dmenu" . hsc3-dmenu-ugen-all)) (define-key map [menu-bar hsc3 expression xmenu-ugen] '("Ugen xmenu" . hsc3-xmenu-ugen-all)) (define-key map [menu-bar hsc3 expression draw-region] '("Draw region" . hsc3-draw-region)) (define-key map [menu-bar hsc3 expression play-region] '("Play region" . hsc3-play-region)) (define-key map [menu-bar hsc3 scsynth] (cons "ScSynth" (make-sparse-keymap "ScSynth"))) (define-key map [menu-bar hsc3 scsynth quit] '("Quit scsynth" . hsc3-quit-scsynth)) (define-key map [menu-bar hsc3 scsynth status] '("Display status" . hsc3-status-scsynth)) (define-key map [menu-bar hsc3 scsynth reset] '("Reset scsynth" . hsc3-reset-scsynth)) (define-key map [menu-bar hsc3 haskell] (cons "Haskell" (make-sparse-keymap "Haskell"))) (define-key map [menu-bar hsc3 haskell quit-haskell] '("Quit haskell" . hsc3-quit-haskell)) (define-key map [menu-bar hsc3 haskell interrupt-haskell] '("Interrupt haskell" . hsc3-interrupt-haskell)) (define-key map [menu-bar hsc3 haskell see-haskell] '("See haskell" . hsc3-see-haskell))) (if hsc3-mode-map () (let ((map (make-sparse-keymap "Haskell-SuperCollider"))) (hsc3-mode-keybindings map) (hsc3-mode-menu map) (setq hsc3-mode-map map))) (define-derived-mode hsc3-mode haskell-mode "Haskell SuperCollider" "Major mode for interacting with an inferior hsc3 process." (turn-on-font-lock)) (add-to-list 'auto-mode-alist '("\\.hs$" . hsc3-mode)) (provide 'hsc3) ;;; hsc3.el ends here