Hi Ben,
Here is the file you are referring to:
What flavor/version of emacs are you using, as that may be important?
I don't use emacs, but checking with ChatGPT, it says the file has some outdated code and suggests this update:
;;; kern-mode.el --- Major mode for editing Humdrum **kern files
(require 'cl-lib)
(defvar kern-mode-hook nil)
(defvar kern-mode-map
(let ((map (make-keymap)))
(define-key map (kbd "C-j") 'newline-and-indent)
(define-key map [tab] 'kern-insert-tab)
(define-key map (kbd "C-M-n") 'next-measure)
(define-key map (kbd "C-M-p") 'previous-measure)
(define-key map (kbd "C-M-m") 'go-to-measure)
(define-key map (kbd "C-M-h") 'show-header)
map)
"Keymap for kern-mode.")
(add-to-list 'auto-mode-alist '("\\.krn\\'" . kern-mode))
(defun end-of-line-p ()
(or (null (char-after)) (char-equal (char-after) ?\n)))
(defun blank-line-p ()
(and (= (current-column) 0)
(end-of-line-p)))
(defun next-measure ()
(interactive)
(when (re-search-forward "^=" nil t)
(beginning-of-line)))
(defun previous-measure ()
(interactive)
(when (re-search-backward "^=" nil t)
(beginning-of-line)))
(defun show-header ()
(interactive)
(save-excursion
(goto-char (point-min))
(when (re-search-forward "^\\*\\*.*$" nil t)
(let ((header-line (buffer-substring (line-beginning-position)
(line-end-position))))
(message "%s" header-line)))))
(defun go-to-measure ()
(interactive)
(let ((m (read-from-minibuffer "measure #: ")))
(if (re-search-forward (concat "^=" m) nil t)
(beginning-of-line)
(when (re-search-backward (concat "^=" m) nil t)
(beginning-of-line)))))
(defun copy-line ()
(interactive)
(kill-new (buffer-substring (line-beginning-position) (line-end-position))))
(defun kern-newline ()
(interactive "*")
(unless (= (line-number-at-pos) 1)
(save-excursion
(forward-line -1)
(let ((prev (buffer-substring (point) (line-end-position))))
(cl-loop repeat (cl-count ?\t prev) do (insert "\t."))))
(delete-horizontal-space))
(newline))
(defun kill-column ()
(interactive)
(when (re-search-backward "^\\*\\*" nil t)
(let ((start (point)))
(when (re-search-forward "^\\*-\\t" nil t)
(kill-rectangle start (point))))))
(defun newline-insert-measure ()
(interactive)
(let (last-measure row)
(save-excursion
(setq row (buffer-substring (line-beginning-position) (line-end-position))))
(save-excursion
(when (re-search-backward "=\\([0-9]+\\)$" nil t)
(cl-destructuring-bind (num . _) (read-from-string (match-string 1))
(setq last-measure (1+ num)))))
(newline)
(let ((fields (cl-count ?\t row)))
(cl-loop for i from 1 to fields do
(insert (format "=%d\t" last-measure)))
(insert (format "=%d" last-measure)))))
(defun kern-insert-tab ()
(interactive)
(let ((last-char (char-before)))
(cond
((blank-line-p)
(message "You need to insert some code."))
((and (char-after) (char-equal (char-after) ?!))
(when (re-search-forward "\\*\\*" nil t)
(backward-char 2)))
((and (char-equal last-char ?\t) (end-of-line-p))
(message "You need to insert some code."))
((not (end-of-line-p))
(re-search-forward "\t" nil t))
(t (insert "\t")))))
(defgroup kern-faces nil
"Faces for Humdrum **kern mode"
:group 'faces)
(defface kern-bar-face
'((t (:foreground "yellow" :background "dim gray")))
"Face for barlines"
:group 'kern-faces)
(defface kern-global-comment-face
'((t (:foreground "firebrick")))
"Face for global comments (!...)"
:group 'kern-faces)
(defface kern-exclusive-face
'((t (:foreground "blue1")))
"Face for exclusive interpretation (**...)"
:group 'kern-faces)
(defface kern-interpretation-face
'((t (:foreground "forest green")))
"Face for other interpretations (*...)"
:group 'kern-faces)
(defface kern-notes
'((t (:foreground "orange")))
"Face for notes and durations"
:group 'kern-faces)
(defvar kern-font-lock-keywords
'(("^!.*" . 'kern-global-comment-face)
("^\\*\\*.*" . 'kern-exclusive-face)
("^\\*[^*].*" . 'kern-interpretation-face)
("^=\\([0-9]+\\)?[:!|]*$" . 'kern-bar-face)
("[0-9.]+[a-grA-G#n-]+" . 'kern-notes))
"Font-lock keywords for kern-mode.")
(defun kern-align-tabs-region (start end)
"Align selected region to tab stops. Replaces spaces with tabs."
(interactive "r")
(let ((tab-width 12))
(untabify start end)
(align-regexp start end "\\(\\s-*\\)\t" 1 1 t)
(tabify start end)))
(defun kern-mode ()
"Major mode for editing Humdrum **kern files."
(interactive)
(kill-all-local-variables)
(use-local-map kern-mode-map)
(setq-local font-lock-defaults '(kern-font-lock-keywords))
(setq-local indent-line-function 'indent-to-left-margin)
(setq-local indent-tabs-mode t)
(setq-local tab-width 12)
(setq-local tab-stop-list (number-sequence 12 240 12))
(setq-local comment-start "!")
(setq major-mode 'kern-mode)
(setq mode-name "KERN")
(setq-local imenu-generic-expression
'(("Barline" "^=\\([0-9]+\\)" 1)
("Interpretation" "^\\*\\*\\(.*\\)" 1)))
(run-hooks 'kern-mode-hook))
(provide 'kern-mode)
;;; kern-mode.el ends here
If that works for you, then I can update the file on the repository.
Documentation (provided by ChatGPT):
📄 kern-mode.el
for Humdrum **kern
Files
🔧 Features Overview
-
Syntax highlighting:
-
!!...
global comments → firebrick
-
**...
exclusive interpretations → blue
-
*...
regular interpretations → forest green
-
=...
barlines → yellow on gray
-
4c
, 2g#
, 1r
(note+duration) → orange
-
Custom keybindings:
-
Smart tab navigation:
-
Measure insertion:
-
Newline auto-fill:
-
Region alignment:
-
Imenu support:
🧰 Installation and Setup Instructions
✅ 1. Save the Mode File
Save the updated file as:
~/.emacs.d/lisp/kern-mode.el
Or any path you prefer.
✅ 2. Edit Your .emacs
or init.el
Add the following:
(add-to-list 'load-path "~/.emacs.d/lisp/") ; adjust path if needed
(require 'kern-mode)
(add-to-list 'auto-mode-alist '("\\.krn\\'" . kern-mode))
This will:
✅ 3. Usage
Open a .krn
file. You should see:
-
Syntax highlighting for Humdrum structures
-
Spines aligned under tab stops
-
Ability to jump or insert measures
⚙️ Optional: Keybinding for Region Alignment
If you want to bind M-x kern-align-tabs-region
to a key (e.g. C-c C-a
), add this:
(define-key kern-mode-map (kbd "C-c C-a") 'kern-align-tabs-region)
Add it near the end of kern-mode
function or in your Emacs config.
🧪 Testing Tips
-
Try opening a .krn
file and press C-M-n
and C-M-p
to jump between =barlines
.
-
Press RET
to insert a new line that mimics tab alignment with .
placeholders.
-
Use M-x imenu
to jump to sections or barlines.
-
Select a misaligned region and run M-x kern-align-tabs-region
.
-=+Craig