all messages for Emacs-related lists mirrored at yhetil.org
 help / color / mirror / code / Atom feed
blob e14ba97f94789e8e3bba081ff6ba364121b9b8ed 9988 bytes (raw)
name: packages/midi-kbd/midi-kbd.el 	 # note: path name is non-authoritative(*)

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
 
;;; midi-kbd.el --- Create keyboard events from Midi input  -*- lexical-binding: t; -*-

;; Copyright (C) 2015  David Kastrup

;; Author: David Kastrup <dak@gnu.org>
;; Keywords: convenience, hardware, multimedia
;; Version: 0.2
;; Maintainer: David Kastrup <dak@gnu.org>

;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.

;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;; GNU General Public License for more details.

;; You should have received a copy of the GNU General Public License
;; along with this program.  If not, see <http://www.gnu.org/licenses/>.

;;; Commentary:

;; Entry point of this package is M-x midikbd-open RET
;;
;; It opens a raw ALSA midi device (see its documentation for how to
;; deal with non-raw devices) and feeds MIDI note-on and note-off
;; events into the Emacs input queue associated with the terminal from
;; which midikbd-open has been called.  Macro recording and replay is
;; possible.  The interpretation of such events is left to
;; applications establishing appropriate key bindings.
;;
;; Since macro recording and replay makes it very desirable to have
;; every generated event be interpretable standalone rather than split
;; into several Emacs events, every MIDI event is encoded into one
;; mouse-like event similar to <Ch1 C_4>.  Consequently, the following
;; functions are applicable to such events:
;;
;; (event-start EVENT) returns the down event part
;; (event-end EVENT) returns the up event part
;;
;; The up event is only available with bindings of <Ch1 up-C-4> and
;; similar, whereas the down event is available for all bindings.
;;
;; up/down event parts may be further split with
;;
;; (posn-area EV) returns a channel symbol Ch1..Ch16
;;
;; (posn-x-y EV) returns numeric values 0..127 for pitch and velocity
;;
;; (posn-timestamp EV) returns a millisecond time value that will wrap
;; around when reaching most-positive-fixnum, about every 12 days on a
;; 32bit system.
;;
;; Note events (omitting the channel modifier) are
;; <C_-1> <Csharp_-1> ... <G_9>
;;
;; Since Midi does not encode enharmonics, there are no *flat_* key
;; names: it is the job of the key bindings to give a higher level
;; interpretation to the basic pitch.

;;; Code:


(defconst midikbd-notenames
  (vconcat
   (cl-loop for i from 0 to 127
	    collect (intern
		     (format "%s_%d"
			     (aref ["C" "Csharp" "D" "Dsharp" "E" "F"
				    "Fsharp" "G" "Gsharp" "A" "Asharp" "B"]
				   (mod i 12))
			     (1- (/ i 12)))))))

;; Necessary to allow bindings to <Ch1 C_4> without splitting events
(cl-loop for key across midikbd-notenames do
	 (put key 'event-kind 'mouse-click))

;; We have `midikbd-notenames' for looking up the basic note name
;; events, `midikbd-upnames' for the keyrelease events, and
;; `midikbd-downnames' for the keypress events.  Those will, for now,
;; produce the likes of `C_-1', `up-C_-1', and `C_-1': we don't
;; actually use `down-C_-1' since the down-event is the principally
;; important one most likely to be bound to keys.

(defconst midikbd-downnames midikbd-notenames)

(defconst midikbd-upnames
  (vconcat
   (cl-loop for i across midikbd-notenames
	    collect
	    (intern (concat "up-" (symbol-name i))))))

;; Emacs can deal with up-events like with down-events since the patch
;; in <URL:http://debbugs.gnu.org/cgi/bugreport.cgi?bug=19746> has
;; been committed to Emacs.
;;
;; Older versions will erupt in violence when forced to deal with an
;; uncached "up-" event, so we need to put the full cache in place
;; ourselves.  We do this only if we find Emacs unable to identify
;; up-events.

;; Calling event-modifiers may poison the cache for up-C_-1 but since
;; we overwrite it first thing afterwards, this is not really an
;; issue.

(unless (event-modifiers 'up-C_-1)
  (cl-loop for key across midikbd-upnames for base across midikbd-notenames
	   do
	   (put key 'event-symbol-element-mask (list base 1))
	   (put key 'event-symbol-elements (list base 'up))
	   (let ((modc (get base 'modifier-cache)))
	     (unless (assq 1 modc)
	       (put base 'modifier-cache (cons (cons 1 key) modc))))))


(defconst midikbd-channelnames
  [Ch1 Ch2 Ch3 Ch4 Ch5 Ch6 Ch7 Ch8
       Ch9 Ch10 Ch11 Ch12 Ch13 Ch14 Ch15 Ch16])

;; CCL programs used in coding systems apparently don't save registers
;; across suspension so we don't use a coding system.  Instead our CCL
;; program is run using ccl-execute-on-string in the filter routine.
;; That allows us to interpret all _completed_ Midi commands without
;; getting confused, and it also gives us a well-defined internal
;; state (namely one for every call of midikbd-filter-create).

;; Decoding Midi is a positive nuisance because of "running status":
;; if a Midi command byte is the same as the last one, it can be
;; omitted and just the data sent.
;; So we keep the current command in r0, the currently read byte in r1,
;; the channel in r6.

(define-ccl-program midikbd-decoder
  '(2
    (loop
     (loop
      ;; central message receiver loop here.
      ;; When it exits, the command to deal with is in r0
      ;; Any arguments are in r1 and r2
      ;; r3 contains: 0 if no arguments are accepted
      ;;              1 if 1 argument can be accepted
      ;;              2 if 2 arguments can be accepted
      ;;              3 if the first of two arguments has been accepted
      ;; Arguments are read into r1 and r2.
      ;; r4 contains the current running status byte if any.
      (read-if (r0 < #x80)
	       (branch r3
		       (repeat)
		       ((r1 = r0) (r0 = r4) (break))
		       ((r1 = r0) (r3 = 3) (repeat))
		       ((r2 = r0) (r3 = 2) (r0 = r4) (break))))
      (if (r0 >= #xf8) ; real time message
	  (break))
      (if (r0 < #xf0) ; channel command
	  ((r4 = r0)
	   (if ((r0 & #xe0) == #xc0)
	       ;; program change and channel pressure take only 1 argument
	       (r3 = 1)
	     (r3 = 2))
	   (repeat)))
      ;; system common message, we swallow those for now
      (r3 = 0)
      (repeat))
     (if ((r0 & #xf0) == #x90)
	 (if (r2 == 0)		    ; Some Midi devices use velocity 0
					; for switching notes off,
					; so translate into note-off
					; and fall through
	     (r0 -= #x10)
	   ((r0 &= #xf)
	    (write 0)
	    (write r0 r1 r2)
	    (repeat))))
     (if ((r0 & #xf0) == #x80)
	 ((r0 &= #xf)
	  (write 1)
	  (write r0 r1 r2)
	  (repeat)))
     (repeat))))

(defun midikbd-get-ts-lessp (pivot)
  "Return a comparison operator for timestamps close to PIVOT.

Timestamps are just a millisecond count that wraps around
eventually.  To compare two timestamps TS1 and TS2, one can
generally just look at the sign of their difference.  However,
this relation is not really transitive when given input spanning
more than half of the given number range (should only happen in
degenerate cases since the overall range spans several days).

Sort algorithms may require transitivity in order to complete, so
this routine creates a transitive comparison operator when given
a \"pivot\" from within the sorted range."
  (lambda (ts1 ts2)
    (< (- ts1 pivot) (- ts2 pivot))))

(defun midikbd-filter-create ()
  "Create one Midi process filter keeping state across calls."
  (let* ((state (make-vector 9 nil))
	 (keypress (make-vector 2048 nil))
	 (param-len [3 3])
	 (hooks (vector
		 (lambda (ts ch pitch velocity)
		   (let ((res
			  (list (aref midikbd-downnames pitch)
				(list nil
				      (aref midikbd-channelnames ch)
				      (cons pitch velocity)
				      ts))))
		     (aset keypress (+ (* ch 128) pitch) res)
		     (list res)))
		 (lambda (ts ch pitch velocity)
		   (let* ((idx (+ (* ch 128) pitch))
			  (oldpress (prog1 (aref keypress idx)
				      (aset keypress idx nil))))
		     (and oldpress
			  (list
			   (list (aref midikbd-upnames pitch)
				 (cadr oldpress)
				 (list nil
				       (aref midikbd-channelnames ch)
				       (cons pitch velocity)
				       ts)))))))))
    (lambda (_process string)
      (let* ((ct (current-time))
	     (ts (+ (* (nth 0 ct) 65536000)
		    (* (nth 1 ct) 1000)
		    (/ (nth 2 ct) 1000)))
	     (str (ccl-execute-on-string 'midikbd-decoder
					 state string t t)))
	(setq unread-command-events
	      (append unread-command-events
		      (cl-loop with i = 0 while (< i (length str))
			       nconc
			       (let* ((code (aref str i))
				      (beg (1+ i)))
				 (setq i (+ beg (aref param-len	code)))
				 (apply (aref hooks code)
					ts
					(append (substring str beg i)
						nil))))))))))

(defcustom midikbd-default-device
  "/dev/snd/midiC1D0"
  "Default MIDI raw device for midikbd."
  :type '(file)
  :group 'midi-kbd
  :package-version '(MIDI-kbd . "0.2"))

;;;###autoload
(defun midikbd-open (file)
  "Open the raw Midi device FILE as a source for Midi input.
This should be an ALSA device like \"/dev/snd/midiC1D0\".  If your
Midi producing device is a software Midi device, you might need to
call

    sudo modprobe snd-virmidi

in order to have some virtual ALSA ports available as such raw Midi
devices."
  (interactive (list (read-file-name "Midi device: "
				     (file-name-directory midikbd-default-device)
				     (file-name-nondirectory midikbd-default-device)
				     t nil
				     #'file-readable-p)))
  (let* ((file (expand-file-name file
				 (file-name-directory midikbd-default-device)))
	 (buffer (get-buffer-create (concat " *Midi process " file " *")))
	 (oldproc (get-buffer-process buffer)))
    (if (processp oldproc) (delete-process oldproc))
    (make-serial-process :port file
			 :speed nil
			 :buffer buffer
			 :coding 'raw-text
			 :filter (midikbd-filter-create)
			 :sentinel #'ignore
			 :noquery t)))

(provide 'midi-kbd)
;;; midi-kbd.el ends here

debug log:

solving e14ba97 ...
found e14ba97 in https://yhetil.org/emacs/87si5k3vtt.fsf@fencepost.gnu.org/

applying [1/1] https://yhetil.org/emacs/87si5k3vtt.fsf@fencepost.gnu.org/
diff --git a/packages/midi-kbd/midi-kbd.el b/packages/midi-kbd/midi-kbd.el
new file mode 100644
index 0000000..e14ba97

Checking patch packages/midi-kbd/midi-kbd.el...
Applied patch packages/midi-kbd/midi-kbd.el cleanly.

index at:
100644 e14ba97f94789e8e3bba081ff6ba364121b9b8ed	packages/midi-kbd/midi-kbd.el

(*) Git path names are given by the tree(s) the blob belongs to.
    Blobs themselves have no identifier aside from the hash of its contents.^

Code repositories for project(s) associated with this external index

	https://git.savannah.gnu.org/cgit/emacs.git
	https://git.savannah.gnu.org/cgit/emacs/org-mode.git

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.