]> code.delx.au - gnu-emacs-elpa/blob - packages/midi-kbd/midi-kbd.el
Merge commit '0cda39255827f283e7578cd469ae42daad9556a2' from js2-mode
[gnu-emacs-elpa] / packages / midi-kbd / midi-kbd.el
1 ;;; midi-kbd.el --- Create keyboard events from Midi input -*- lexical-binding: t; -*-
2
3 ;; Copyright (C) 2015 Free Software Foundation, Inc.
4
5 ;; Author: David Kastrup <dak@gnu.org>
6 ;; Keywords: convenience, hardware, multimedia
7 ;; Version: 0.2
8 ;; Maintainer: David Kastrup <dak@gnu.org>
9 ;; Package-Requires: ((emacs "25"))
10
11 ;; This program is free software; you can redistribute it and/or modify
12 ;; it under the terms of the GNU General Public License as published by
13 ;; the Free Software Foundation, either version 3 of the License, or
14 ;; (at your option) any later version.
15
16 ;; This program is distributed in the hope that it will be useful,
17 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
18 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 ;; GNU General Public License for more details.
20
21 ;; You should have received a copy of the GNU General Public License
22 ;; along with this program. If not, see <http://www.gnu.org/licenses/>.
23
24 ;;; Commentary:
25
26 ;; Entry point of this package is M-x midikbd-open RET
27 ;;
28 ;; It opens a raw ALSA midi device (see its documentation for how to
29 ;; deal with non-raw devices) and feeds MIDI note-on and note-off
30 ;; events into the Emacs input queue associated with the terminal from
31 ;; which midikbd-open has been called. Macro recording and replay is
32 ;; possible. The interpretation of such events is left to
33 ;; applications establishing appropriate key bindings.
34 ;;
35 ;; Since macro recording and replay makes it very desirable to have
36 ;; every generated event be interpretable standalone rather than split
37 ;; into several Emacs events, every MIDI event is encoded into one
38 ;; mouse-like event similar to <Ch1 C_4>. Consequently, the following
39 ;; functions are applicable to such events:
40 ;;
41 ;; (event-start EVENT) returns the down event part
42 ;; (event-end EVENT) returns the up event part
43 ;;
44 ;; The up event is only available with bindings of <Ch1 up-C-4> and
45 ;; similar, whereas the down event is available for all bindings.
46 ;;
47 ;; up/down event parts may be further split with
48 ;;
49 ;; (posn-area EV) returns a channel symbol Ch1..Ch16
50 ;;
51 ;; (posn-x-y EV) returns numeric values 0..127 for pitch and velocity
52 ;;
53 ;; (posn-timestamp EV) returns a millisecond time value that will wrap
54 ;; around when reaching most-positive-fixnum, about every 12 days on a
55 ;; 32bit system.
56 ;;
57 ;; Note events (omitting the channel modifier) are
58 ;; <C_-1> <Csharp_-1> ... <G_9>
59 ;;
60 ;; Since Midi does not encode enharmonics, there are no *flat_* key
61 ;; names: it is the job of the key bindings to give a higher level
62 ;; interpretation to the basic pitch.
63
64 ;;; Code:
65
66
67 (defconst midikbd-notenames
68 (vconcat
69 (cl-loop for i from 0 to 127
70 collect (intern
71 (format "%s_%d"
72 (aref ["C" "Csharp" "D" "Dsharp" "E" "F"
73 "Fsharp" "G" "Gsharp" "A" "Asharp" "B"]
74 (mod i 12))
75 (1- (/ i 12)))))))
76
77 ;; Necessary to allow bindings to <Ch1 C_4> without splitting events
78 (cl-loop for key across midikbd-notenames do
79 (put key 'event-kind 'mouse-click))
80
81 ;; We have `midikbd-notenames' for looking up the basic note name
82 ;; events, `midikbd-upnames' for the keyrelease events, and
83 ;; `midikbd-downnames' for the keypress events. Those will, for now,
84 ;; produce the likes of `C_-1', `up-C_-1', and `C_-1': we don't
85 ;; actually use `down-C_-1' since the down-event is the principally
86 ;; important one most likely to be bound to keys.
87
88 (defconst midikbd-downnames midikbd-notenames)
89
90 (defconst midikbd-upnames
91 (vconcat
92 (cl-loop for i across midikbd-notenames
93 collect
94 (intern (concat "up-" (symbol-name i))))))
95
96 ;; Emacs can deal with up-events like with down-events since the patch
97 ;; in <URL:http://debbugs.gnu.org/cgi/bugreport.cgi?bug=19746> has
98 ;; been committed to Emacs.
99 ;;
100 ;; Older versions will erupt in violence when forced to deal with an
101 ;; uncached "up-" event, so we need to put the full cache in place
102 ;; ourselves. We do this only if we find Emacs unable to identify
103 ;; up-events.
104
105 ;; Calling event-modifiers may poison the cache for up-C_-1 but since
106 ;; we overwrite it first thing afterwards, this is not really an
107 ;; issue.
108
109 (unless (event-modifiers 'up-C_-1)
110 (cl-loop for key across midikbd-upnames for base across midikbd-notenames
111 do
112 (put key 'event-symbol-element-mask (list base 1))
113 (put key 'event-symbol-elements (list base 'up))
114 (let ((modc (get base 'modifier-cache)))
115 (unless (assq 1 modc)
116 (put base 'modifier-cache (cons (cons 1 key) modc))))))
117
118
119 (defconst midikbd-channelnames
120 [Ch1 Ch2 Ch3 Ch4 Ch5 Ch6 Ch7 Ch8
121 Ch9 Ch10 Ch11 Ch12 Ch13 Ch14 Ch15 Ch16])
122
123 ;; CCL programs used in coding systems apparently don't save registers
124 ;; across suspension so we don't use a coding system. Instead our CCL
125 ;; program is run using ccl-execute-on-string in the filter routine.
126 ;; That allows us to interpret all _completed_ Midi commands without
127 ;; getting confused, and it also gives us a well-defined internal
128 ;; state (namely one for every call of midikbd-filter-create).
129
130 ;; Decoding Midi is a positive nuisance because of "running status":
131 ;; if a Midi command byte is the same as the last one, it can be
132 ;; omitted and just the data sent.
133 ;; So we keep the current command in r0, the currently read byte in r1,
134 ;; the channel in r6.
135
136 (define-ccl-program midikbd-decoder
137 '(2
138 (loop
139 (loop
140 ;; central message receiver loop here.
141 ;; When it exits, the command to deal with is in r0
142 ;; Any arguments are in r1 and r2
143 ;; r3 contains: 0 if no arguments are accepted
144 ;; 1 if 1 argument can be accepted
145 ;; 2 if 2 arguments can be accepted
146 ;; 3 if the first of two arguments has been accepted
147 ;; Arguments are read into r1 and r2.
148 ;; r4 contains the current running status byte if any.
149 (read-if (r0 < #x80)
150 (branch r3
151 (repeat)
152 ((r1 = r0) (r0 = r4) (break))
153 ((r1 = r0) (r3 = 3) (repeat))
154 ((r2 = r0) (r3 = 2) (r0 = r4) (break))))
155 (if (r0 >= #xf8) ; real time message
156 (break))
157 (if (r0 < #xf0) ; channel command
158 ((r4 = r0)
159 (if ((r0 & #xe0) == #xc0)
160 ;; program change and channel pressure take only 1 argument
161 (r3 = 1)
162 (r3 = 2))
163 (repeat)))
164 ;; system common message, we swallow those for now
165 (r3 = 0)
166 (repeat))
167 (if ((r0 & #xf0) == #x90)
168 (if (r2 == 0) ; Some Midi devices use velocity 0
169 ; for switching notes off,
170 ; so translate into note-off
171 ; and fall through
172 (r0 -= #x10)
173 ((r0 &= #xf)
174 (write 0)
175 (write r0 r1 r2)
176 (repeat))))
177 (if ((r0 & #xf0) == #x80)
178 ((r0 &= #xf)
179 (write 1)
180 (write r0 r1 r2)
181 (repeat)))
182 (repeat))))
183
184 (defun midikbd-get-ts-lessp (pivot)
185 "Return a comparison operator for timestamps close to PIVOT.
186
187 Timestamps are just a millisecond count that wraps around
188 eventually. To compare two timestamps TS1 and TS2, one can
189 generally just look at the sign of their difference. However,
190 this relation is not really transitive when given input spanning
191 more than half of the given number range (should only happen in
192 degenerate cases since the overall range spans several days).
193
194 Sort algorithms may require transitivity in order to complete, so
195 this routine creates a transitive comparison operator when given
196 a \"pivot\" from within the sorted range."
197 (lambda (ts1 ts2)
198 (< (- ts1 pivot) (- ts2 pivot))))
199
200 (defun midikbd-filter-create ()
201 "Create one Midi process filter keeping state across calls."
202 (let* ((state (make-vector 9 nil))
203 (keypress (make-vector 2048 nil))
204 (param-len [3 3])
205 (hooks (vector
206 (lambda (ts ch pitch velocity)
207 (let ((res
208 (list (aref midikbd-downnames pitch)
209 (list nil
210 (aref midikbd-channelnames ch)
211 (cons pitch velocity)
212 ts))))
213 (aset keypress (+ (* ch 128) pitch) res)
214 (list res)))
215 (lambda (ts ch pitch velocity)
216 (let* ((idx (+ (* ch 128) pitch))
217 (oldpress (prog1 (aref keypress idx)
218 (aset keypress idx nil))))
219 (and oldpress
220 (list
221 (list (aref midikbd-upnames pitch)
222 (cadr oldpress)
223 (list nil
224 (aref midikbd-channelnames ch)
225 (cons pitch velocity)
226 ts)))))))))
227 (lambda (_process string)
228 (let* ((ct (current-time))
229 (ts (+ (* (nth 0 ct) 65536000)
230 (* (nth 1 ct) 1000)
231 (/ (nth 2 ct) 1000)))
232 (str (ccl-execute-on-string 'midikbd-decoder
233 state string t t)))
234 (setq unread-command-events
235 (append unread-command-events
236 (cl-loop with i = 0 while (< i (length str))
237 nconc
238 (let* ((code (aref str i))
239 (beg (1+ i)))
240 (setq i (+ beg (aref param-len code)))
241 (apply (aref hooks code)
242 ts
243 (append (substring str beg i)
244 nil))))))))))
245
246 (defcustom midikbd-default-device
247 "/dev/snd/midiC1D0"
248 "Default MIDI raw device for midikbd."
249 :type '(file)
250 :group 'midi-kbd
251 :package-version '(midi-kbd . "0.2"))
252
253 ;;;###autoload
254 (defun midikbd-open (file)
255 "Open the raw Midi device FILE as a source for Midi input.
256 This should be an ALSA device like \"/dev/snd/midiC1D0\". If your
257 Midi producing device is a software Midi device, you might need to
258 call
259
260 sudo modprobe snd-virmidi
261
262 in order to have some virtual ALSA ports available as such raw Midi
263 devices."
264 (interactive (list (read-file-name "Midi device: "
265 (file-name-directory midikbd-default-device)
266 (file-name-nondirectory midikbd-default-device)
267 t nil
268 #'file-readable-p)))
269 (let* ((file (expand-file-name file
270 (file-name-directory midikbd-default-device)))
271 (buffer (get-buffer-create (concat " *Midi process " file " *")))
272 (oldproc (get-buffer-process buffer)))
273 (if (processp oldproc) (delete-process oldproc))
274 (make-serial-process :port file
275 :speed nil
276 :buffer buffer
277 :coding 'raw-text
278 :filter (midikbd-filter-create)
279 :sentinel #'ignore
280 :noquery t)))
281
282 (provide 'midi-kbd)
283 ;;; midi-kbd.el ends here