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: Integrating Midi into Emacs Date: Wed, 14 Jan 2015 12:20:53 +0100 Message-ID: <87zj9lfw16.fsf@fencepost.gnu.org> References: <87fvbttoa9.fsf@fencepost.gnu.org> <87lhlbmx7l.fsf@fencepost.gnu.org> <878uh5hc1t.fsf@fencepost.gnu.org> NNTP-Posting-Host: plane.gmane.org Mime-Version: 1.0 Content-Type: multipart/mixed; boundary="=-=-=" X-Trace: ger.gmane.org 1421234476 11279 80.91.229.3 (14 Jan 2015 11:21:16 GMT) X-Complaints-To: usenet@ger.gmane.org NNTP-Posting-Date: Wed, 14 Jan 2015 11:21:16 +0000 (UTC) Cc: emacs-devel@gnu.org To: Niels Giesen Original-X-From: emacs-devel-bounces+ged-emacs-devel=m.gmane.org@gnu.org Wed Jan 14 12:21:08 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 1YBM0V-0002kL-TW for ged-emacs-devel@m.gmane.org; Wed, 14 Jan 2015 12:21:04 +0100 Original-Received: from localhost ([::1]:45631 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1YBM0V-0005J9-0j for ged-emacs-devel@m.gmane.org; Wed, 14 Jan 2015 06:21:03 -0500 Original-Received: from eggs.gnu.org ([2001:4830:134:3::10]:36319) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1YBM0Q-0005Id-9c for emacs-devel@gnu.org; Wed, 14 Jan 2015 06:20:59 -0500 Original-Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1YBM0M-0002Wr-5o for emacs-devel@gnu.org; Wed, 14 Jan 2015 06:20:58 -0500 Original-Received: from fencepost.gnu.org ([2001:4830:134:3::e]:35085) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1YBM0M-0002Wm-12 for emacs-devel@gnu.org; Wed, 14 Jan 2015 06:20:54 -0500 Original-Received: from localhost ([127.0.0.1]:42260 helo=lola) by fencepost.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1YBM0L-0004lU-GR; Wed, 14 Jan 2015 06:20:53 -0500 Original-Received: by lola (Postfix, from userid 1000) id 1776BE048C; Wed, 14 Jan 2015 12:20:53 +0100 (CET) In-Reply-To: <878uh5hc1t.fsf@fencepost.gnu.org> (David Kastrup's message of "Wed, 14 Jan 2015 11:49:34 +0100") 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:181249 Archived-At: --=-=-= Content-Type: text/plain David Kastrup writes: > Niels Giesen writes: > >> Hi David, >> >> This looks really cool to me. I tried to use your code, but cannot for the >> life of me find out how to bind functions to those events. Could you give a >> simple example? > > Well, this is work in progress. I've been tripped up by quirks of the > raw Midi spec: to make things more challenging, it allows inserting > foreign command bytes ("realtime events") in the middle of other > commands. And it allows leaving off command bytes altogether ("running > status byte"). When accessing Midi devices through libraries or the > Midi API, those peculiarities are taken care off, but not so with the > raw Midi stream. > > I'm appending the current files I am working with. They are _not_ a > finished application yet. The midikbd module produces events. The > decoding framework should now be working reliably, but currently only > the events I am interested in working with get produced. And the > lily-midi module integrates this into LilyPond-mode, assuming that it > has already been loaded (again: work in progress, so load order, > autoloads etc are all not in place). The notenames are hardwired to > dutch, channel changes are not currently used, timing is not recorded > and so on. The last thing I did was implementing a recording key > signature so that pitches are produced with accidentals chosen in a > useful relation to the key signature. > > The long pitch would be to use libmidiport in Emacs proper, and produce > both key up and down events (with at least the up events ignored by > default) and full timing info, similar to mouse events. Most of this > (apart from the default binding behavior) can probably be presimulated > by extending midi-kbd.el, but the precision of the timing would likely > suffer. Oh fiddlesticks. Should have appended the files. --=-=-= Content-Type: application/emacs-lisp Content-Disposition: inline; filename=midi-kbd.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: ;; Should this be a char-table? (defvar midikbd-keys [C_-1 Csharp_-1 D_-1 Dsharp_-1 E_-1 F_-1 Fsharp_-1 G_-1 Gsharp_-1 A_-1 Asharp_-1 B_-1 C_0 Csharp_0 D_0 Dsharp_0 E_0 F_0 Fsharp_0 G_0 Gsharp_0 A_0 Asharp_0 B_0 C_1 Csharp_1 D_1 Dsharp_1 E_1 F_1 Fsharp_1 G_1 Gsharp_1 A_1 Asharp_1 B_1 C_2 Csharp_2 D_2 Dsharp_2 E_2 F_2 Fsharp_2 G_2 Gsharp_2 A_2 Asharp_2 B_2 C_3 Csharp_3 D_3 Dsharp_3 E_3 F_3 Fsharp_3 G_3 Gsharp_3 A_3 Asharp_3 B_3 C_4 Csharp_4 D_4 Dsharp_4 E_4 F_4 Fsharp_4 G_4 Gsharp_4 A_4 Asharp_4 B_4 C_5 Csharp_5 D_5 Dsharp_5 E_5 F_5 Fsharp_5 G_5 Gsharp_5 A_5 Asharp_5 B_5 C_6 Csharp_6 D_6 Dsharp_6 E_6 F_6 Fsharp_6 G_6 Gsharp_6 A_6 Asharp_6 B_6 C_7 Csharp_7 D_7 Dsharp_7 E_7 F_7 Fsharp_7 G_7 Gsharp_7 A_7 Asharp_7 B_7 C_8 Csharp_8 D_8 Dsharp_8 E_8 F_8 Fsharp_8 G_8 Gsharp_8 A_8 Asharp_8 B_8 C_9 Csharp_9 D_9 Dsharp_9 E_9 F_9 Fsharp_9 G_9 Channel1 Channel2 Channel3 Channel4 Channel5 Channel6 Channel7 Channel8 Channel9 Channel10 Channel11 Channel12 Channel13 Channel14 Channel15 Channel16] "Map CCL program codes to symbols used as keycodes. CCL program `midikbd-decoder' produces 0..127 for the pitches and 128..143 for the channel switches. `midikbd-keys' maps this into symbols used as keycodes.") ;; 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 '(1 ((r6 =3D -1) (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 15) (if (r0 !=3D r6) ((r6 =3D r0) (write (r6 + 128)))) (write-repeat r1)))) (repeat))))) (defun midikbd-filter-create () "Create one Midi process filter keeping state across calls." (let ((state (make-vector 9 nil))) (lambda (_process string) (setq unread-command-events (append unread-command-events (mapcar (lambda (x) (aref midikbd-keys x)) (ccl-execute-on-string 'midikbd-decoder state string t t))))))) ;;;###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 --=-=-= Content-Type: application/emacs-lisp Content-Disposition: inline; filename=lily-midi.el Content-Transfer-Encoding: quoted-printable ;;; lily-midi.el --- Use Midi events for inserting LilyPond music -*- lexi= cal-binding: t; -*- ;; Copyright (C) 2015 David Kastrup ;; Author: David Kastrup ;; Keywords: multimedia, convenience, languages ;; 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: ;;=20 ;;; Code: ;; default notenames and modifications: ;; ;; The assignment to Midi names is done using the following table that ;; tries to accomodate some common chords in the C major scale but ;; also melodic A minor. This does not behave all that robustly under ;; modulation. ;; ;; TODO: should we have a more complete picture of the signature or ;; current mode? Conversion to notenames may depend on lead tones and ;; lead harmonies, and those are different for c \major and a \minor, ;; for example. (defconst lily-defaultmidi-names [0 0 1 2 2 3 3 4 4 5 6 6] "Default assignment of notenames to semitones.") (defconst lily-defaultmidi-scale [0 2 4 5 7 9 11] "Semitone offsets for the various notenames.") (defun lily-midi-to-note (note &optional sharps) "Convert NOTE under key SHARPS to an (octave note accidental) triple." ;; the trick here with regard to sharps is that we transpose the ;; index into `lily-defaultmidi-names' by `sharps' chromatic ;; fourths, and transpose the result down again by `sharps' diatonic ;; fourths. We ignore the octaves and accidentals and fix them up ;; at the end. (let* ((sharps (or sharps lily-midi-keysig 0)) (inkey (aref lily-defaultmidi-names (mod (+ note (* sharps 5)) 12))) (name (mod (+ inkey (* sharps 4)) 7)) (nameoff (aref lily-defaultmidi-scale name)) (acc (- (mod (- note nameoff 6) 12) 6)) (oct (/ (- note nameoff acc 60) 12))) (list oct name (/ acc 2.0)))) (defun lily-notename (octave note accidental) "Generate notename from OCTAVE NOTE ACCIDENTAL triple." (concat (aref ["c" "d" "e" "f" "g" "a" "b"] note) (aref ["eses" "eseh" "es" "eh" "" "ih" "is" "isih" "isis"] (round (* 4 (1+ accidental)))) (if (< octave -1) (make-string (- -1 octave) ?\,) (make-string (1+ octave) ?\')))) (defun lily-note-insert (string) "Insert notename STRING into current buffer, possibly preceded by space." (if (memq (char-before) '(nil ?\< ?\ ?\t ?\n)) (insert string) (insert ?\ string))) =20=20=20=20=20=20 (defun lily-pitch-insert-for (note) "Calculate a pitch insertion routine for Midi note NOTE. This takes a look at `lily-midi-keysig' and determines the proper enharmonic variant to insert." (lambda () (interactive) (lily-note-insert (apply #'lily-notename (lily-midi-to-note note))))) (defvar lily-midi-keysig nil ;; nil outside of LilyPond-mode "Key signature for Midi recognition, number of sharps.") (make-variable-buffer-local 'lily-midi-keysig) ;; This is not an actual minor mode, but we still use the minor mode ;; mechanism to display the current Midi key. (add-to-list 'minor-mode-alist '(lily-midi-keysig (:eval (format " %c%d" (if (< lily-midi-keysig 0) ?=E2=99=AD ?=E2=99=AF) (abs lily-midi-keysig))))) (defun LilyPond-midi-key (sharps) "Set Midi key to the number of SHARPS given." (interactive "P") (setq lily-midi-keysig (and sharps (prefix-numeric-value sharps))) (force-mode-line-update)) =09 (define-key LilyPond-mode-map (kbd "C-c #") #'LilyPond-midi-key) (dotimes (note 128) (define-key LilyPond-mode-map (kbd (concat "<" (aref ["C" "Csharp" "D" "Dsharp" "E" "F" "Fsharp" "G" "Gsharp" "A" "Asharp" "B"] (mod note 12)) "_" (number-to-string (1- (/ note 12))) ">")) (lily-pitch-insert-for note))) (dotimes (ch 16) (define-key LilyPond-mode-map (kbd (concat "")) #'ignore)) (provide 'lily-midi) ;;; lily-midi.el ends here --=-=-= Content-Type: text/plain -- David Kastrup --=-=-=--