From mboxrd@z Thu Jan 1 00:00:00 1970 Path: news.gmane.org!not-for-mail From: David Kastrup Newsgroups: gmane.emacs.devel Subject: Re: [PATCH] Let input queue deal gracefully with up-events Date: Wed, 28 Jan 2015 20:50:34 +0100 Message-ID: <87d25yptv9.fsf@fencepost.gnu.org> References: <1422451883-6530-1-git-send-email-dak@gnu.org> NNTP-Posting-Host: plane.gmane.org Mime-Version: 1.0 Content-Type: multipart/mixed; boundary="=-=-=" X-Trace: ger.gmane.org 1422474652 27416 80.91.229.3 (28 Jan 2015 19:50:52 GMT) X-Complaints-To: usenet@ger.gmane.org NNTP-Posting-Date: Wed, 28 Jan 2015 19:50:52 +0000 (UTC) Cc: emacs-devel@gnu.org To: Stefan Monnier Original-X-From: emacs-devel-bounces+ged-emacs-devel=m.gmane.org@gnu.org Wed Jan 28 20:50:52 2015 Return-path: Envelope-to: ged-emacs-devel@m.gmane.org Original-Received: from lists.gnu.org ([208.118.235.17]) by plane.gmane.org with esmtp (Exim 4.69) (envelope-from ) id 1YGYdX-00088s-7s for ged-emacs-devel@m.gmane.org; Wed, 28 Jan 2015 20:50:51 +0100 Original-Received: from localhost ([::1]:55661 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1YGYdW-0007Ln-LI for ged-emacs-devel@m.gmane.org; Wed, 28 Jan 2015 14:50:50 -0500 Original-Received: from eggs.gnu.org ([2001:4830:134:3::10]:57387) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1YGYdK-0007JL-Tn for emacs-devel@gnu.org; Wed, 28 Jan 2015 14:50:40 -0500 Original-Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1YGYdI-0004mG-Qx for emacs-devel@gnu.org; Wed, 28 Jan 2015 14:50:38 -0500 Original-Received: from fencepost.gnu.org ([2001:4830:134:3::e]:34741) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1YGYdI-0004lR-7N for emacs-devel@gnu.org; Wed, 28 Jan 2015 14:50:36 -0500 Original-Received: from localhost ([127.0.0.1]:41914 helo=lola) by fencepost.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1YGYdG-0000x8-SD; Wed, 28 Jan 2015 14:50:35 -0500 Original-Received: by lola (Postfix, from userid 1000) id 59E3ADF6AB; Wed, 28 Jan 2015 20:50:34 +0100 (CET) In-Reply-To: (Stefan Monnier's message of "Wed, 28 Jan 2015 14:35:54 -0500") User-Agent: Gnus/5.13 (Gnus v5.13) Emacs/25.0.50 (gnu/linux) X-detected-operating-system: by eggs.gnu.org: Error: Malformed IPv6 address (bad octet value). X-Received-From: 2001:4830:134:3::e X-BeenThere: emacs-devel@gnu.org X-Mailman-Version: 2.1.14 Precedence: list List-Id: "Emacs development discussions." List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: emacs-devel-bounces+ged-emacs-devel=m.gmane.org@gnu.org Original-Sender: emacs-devel-bounces+ged-emacs-devel=m.gmane.org@gnu.org Xref: news.gmane.org gmane.emacs.devel:181940 Archived-At: --=-=-= Content-Type: text/plain Stefan Monnier writes: >> * keyboard.c (apply_modifiers_uncached, parse_solitary_modifier) >> (parse_modifiers_uncached): React gracefully to "up-" modifiers: >> those may easily be injected by user-level Lisp code. > > Can you provide some context? When would Lisp code inject events with > an "up-" modifier? How else would you want to call the key release from a piano keyboard? It is basically auxiliary information that can often be discarded: the key press event is much more important when using a piano keyboard. I'll append my current context. Note that when you remove the code pre-filling the modifier-cache (and haven't applied the given patch), Emacs will crash when you press (actually when you release) a key on the connected Midi keyboard, generating a [Ch1 up-C_4] event (for example). The long-term goal is to integrate Midi event generation into the Emacs binary. But at the current point of time, I am still experimenting around with figuring out just what information should arrive in what form, and feeding Emacs with such non-native events should be possible as well. If you don't have actual Midi hardware, install vmpk (a virtual Midi keyboard) and do sudo insmod snd_virmidi then use the "Connections" menu of vmpk for connecting the keyboard output to the first virtual Midi device, load the attached file, and then do M-x midikbd-open RET RET in order to have keypresses and releases from the keyboard arrive in Emacs. If the "... is undefined" messages are not helpful enough, try M-: (while t (message "%S" (read-event))) RET to see more details. End with C-g of course. -- David Kastrup --=-=-= Content-Type: application/emacs-lisp Content-Disposition: attachment; filename=midi-kbd3.el Content-Transfer-Encoding: quoted-printable ;;; midi-kbd.el --- Create keyboard events from Midi input -*- lexical-bin= ding: t; -*- ;; Copyright (C) 2015 David Kastrup ;; Author: David Kastrup ;; Keywords: convenience, hardware, multimedia ;; 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 . ;;; Commentary: ;; This generates events from an ALSA Midi device. The names of the ;; events are modeled after the nomenclature of Emacs Calc's notenames. ;; Channel switching events are ... ;; Note Events are ... ;; Since Midi does not encode enharmonics, there are no *flat_* key ;; names: it is the job of the key bindings to establish tonality ;; beyond the chromatic pitch. ;; ;; It would make sense to provide this kind of functionality directly ;; from within Emacs, making use of libportmidi. That way, it could ;; get supported by operating systems not providing a raw Midi port ;; accessible as a character device. This would also improve the ;; possibilities to integrate timing and modifiers into events like it ;; is done with mouse events. ;;; Code: ;; Design decision: we encode the channel numbers as a modifier. That ;; way, one can either bind the channel-specific events to keys, or a ;; channel-less fallback. This may be particularly useful for ;; special-casing channel 10, the drum channel. (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 without splitting events (cl-loop for key across midikbd-notenames do (put key 'event-kind 'mouse-click)) (defconst midikbd-downnames (vconcat (cl-loop for i across midikbd-notenames collect (intern (concat "down-" (symbol-name i)))))) ;; (defconst midikbd-downnames midikbd-notenames) (defconst midikbd-upnames (vconcat (cl-loop for i across midikbd-notenames collect (intern (concat "up-" (symbol-name i)))))) ;; Emacs 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 since ;; a patch has already been committed. Calling event-modifiers may ;; poison the cache for up-C_-1 but in that case we overwrite it first ;; thing afterwards. (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)))))) =09=20 (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 =3D r0) (r0 =3D r4) (break)) ((r1 =3D r0) (r3 =3D 3) (repeat)) ((r2 =3D r0) (r3 =3D 2) (r0 =3D r4) (break)))) (if (r0 >=3D #xf8) ; real time message (break)) (if (r0 < #xf0) ; channel command ((r4 =3D r0) (if ((r0 & #xe0) =3D=3D #xc0) ;; program change and channel pressure take only 1 argument (r3 =3D 1) (r3 =3D 2)) (repeat))) ;; system common message, we swallow those for now (r3 =3D 0) (repeat)) (if ((r0 & #xf0) =3D=3D #x90) (if (r2 =3D=3D 0) ; Some Midi devices use velocity 0 ; for switching notes off, ; so translate into note-off ; and fall through (r0 -=3D #x10) ((r0 &=3D #xf) (write 0) (write r0 r1 r2) (repeat)))) (if ((r0 & #xf0) =3D=3D #x80) ((r0 &=3D #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) res)) (lambda (ts ch pitch velocity) (let* ((idx (+ (* ch 128) pitch)) (oldpress (prog1 (aref keypress idx) (aset keypress idx nil)))) (and oldpress (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) 65536) (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 =3D 0 while (< i (length str)) if (let* ((code (aref str i)) (beg (1+ i)) (end (+ beg (aref param-len code))) (res (apply (aref hooks code) ts (append (substring str beg end) nil)))) (setq i end) res) collect it))))))) ;;;###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: " "/dev/snd/" "midiC1D0" t nil #'file-readable-p))) (let* ((file (expand-file-name file "/dev/snd/")) (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 --=-=-=--