-
Notifications
You must be signed in to change notification settings - Fork 2
/
midimacs-midi.el
276 lines (221 loc) · 11.8 KB
/
midimacs-midi.el
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
(eval-when-compile
(require 'cl))
(require 'midimacs-globals)
(defun midimacs-amidicat-init ()
(interactive)
(loop for direction in '(input output)
when (midimacs-amidicat-proc-is-active direction)
do (midimacs-amidicat-proc-kill direction)
when (midimacs-amidicat-buffer-has-process direction)
do (midimacs-amidicat-proc-kill direction)
when (midimacs-amidicat-buffer-is-open direction)
do (midimacs-amidicat-buffer-close direction))
(midimacs-amidicat-proc-open 'input)
(midimacs-amidicat-proc-open 'output "--noread")
(when (midimacs-amidicat-proc 'input)
(set-process-filter (midimacs-amidicat-proc 'input) 'midimacs-amidicat-read)))
(defun* midimacs-amidicat-proc-open (direction &optional (noread nil))
(let* ((port (midimacs-midi-port direction))
(direction-name (symbol-name direction))
(process-args (list (midimacs-amidicat-proc-name direction)
(midimacs-amidicat-buffer-name direction)
"amidicat" "--hex" "--port" port)))
(when noread
(setq process-args (nconc process-args (list "--noread"))))
(when (not (equal port "DEBUG"))
(apply 'start-process process-args)
(sit-for 0.2) ; wait for proc to start
(unless (midimacs-amidicat-proc-is-active direction)
(display-warning 'midimacs (concat "Failed to open midimacs " direction-name " port " port
", try `M-x customize-variable midimacs-midi-"
direction-name "-port` and `M-! amidicat --list`"))
(midimacs-amidicat-buffer-close direction)))))
(defun midimacs-midi-port (direction)
(cond ((eq direction 'input) midimacs-midi-input-port)
((eq direction 'output) midimacs-midi-output-port)))
(defun midimacs-amidicat-proc-is-active (direction)
(let ((proc (midimacs-amidicat-proc direction)))
(when proc
(member (process-status proc) '(run signal)))))
(defun midimacs-amidicat-buffer-has-process (direction)
(get-buffer-process (midimacs-amidicat-buffer direction)))
(defun midimacs-amidicat-proc-kill (direction)
(delete-process (midimacs-amidicat-proc direction)))
(defun midimacs-amidicat-buffer-is-open (direction)
(midimacs-amidicat-buffer direction))
(defun midimacs-amidicat-buffer (direction)
(get-buffer (midimacs-amidicat-buffer-name direction)))
(defun midimacs-amidicat-buffer-close (direction)
(kill-buffer (midimacs-amidicat-buffer direction)))
(defun midimacs-amidicat-proc (direction)
(get-process (midimacs-amidicat-proc-name direction)))
(defun midimacs-amidicat-proc-name (direction)
;; process same name as buffer
(midimacs-amidicat-buffer-name direction))
(defun midimacs-amidicat-buffer-name (direction)
(concat "*midimacs-amidicat-" (symbol-name direction) "-proc*"))
(defun midimacs-midi-message-note-on (channel pitch velocity)
(make-midimacs-midi-message :status (+ #x90 channel)
:data1 pitch
:data2 velocity))
(defun midimacs-midi-message-note-off (channel pitch velocity)
(make-midimacs-midi-message :status (+ #x80 channel)
:data1 pitch
:data2 velocity))
(defun midimacs-midi-message-program-change (channel program)
(make-midimacs-midi-message :status (+ #xC0 channel)
:data1 program))
(defun midimacs-midi-message-all-notes-off (channel)
(make-midimacs-midi-message :status (+ #xB0 channel)
:data1 #x7B
:data2 0))
(defun midimacs-midi-message-is-note-on (message)
(eq (elt (format "%02X" (midimacs-midi-message-status message)) 0) ?9))
(defun midimacs-midi-message-is-note-off (message)
(let ((status-byte-char-0 (elt (format "%02X" (midimacs-midi-message-status message)) 0)))
(or (eq status-byte-char-0 ?8)
(and (eq status-byte-char-0 ?9)
(eq (midimacs-midi-message-data2 message) 0)))))
(defun midimacs-program-change (channel program)
(midimacs-midi-execute (midimacs-midi-message-program-change channel program)))
(defun midimacs-set-velocity (channel velocity)
(puthash channel (min 127 velocity) midimacs-channel-default-velocities))
(defun midimacs-midi-schedule-note-off (abs-time channel pitch off-velocity)
(heap-add midimacs-scheduled-note-offs (list abs-time channel pitch off-velocity)))
(defun midimacs-midi-execute (message)
(let ((serialized-message (midimacs-midi-serialize message)))
(cond ((midimacs-amidicat-proc-is-active 'output)
(process-send-string (midimacs-amidicat-proc 'output) serialized-message))
((equal midimacs-midi-output-port "DEBUG")
(message (string-trim serialized-message))))))
(defun midimacs-midi-serialize (message)
(let* ((status (midimacs-midi-message-status message))
(data1 (midimacs-midi-message-data1 message))
(data2 (midimacs-midi-message-data2 message))
(status-string (format "%02X" status))
(data1-string (if data1 (format " %02X" data1) ""))
(data2-string (if (and data1 data2) (format " %02X" data2) "")))
(concat status-string data1-string data2-string "\n")))
(defun midimacs-midi-unserialize (s)
(unless (string-prefix-p "Unable to connect to ALSA port" s)
(destructuring-bind (status-s data1-s &optional data2-s)
(split-string s " ")
(make-midimacs-midi-message :status (string-to-number status-s 16)
:data1 (string-to-number data1-s 16)
:data2 (when data2-s (string-to-number data2-s 16))))))
(defun midimacs-delete-message-heap-until (heap pos)
(let ((el))
(loop while (and (setq el (heap-root heap))
(midimacs-time<= (nth 0 el) pos))
collect (heap-delete-root heap))))
(defun midimacs-trigger-note-offs ()
(let ((note-offs (midimacs-delete-message-heap-until midimacs-scheduled-note-offs midimacs-abs-time)))
(loop for (time channel pitch off-velocity) in note-offs
do (midimacs-note-off channel pitch off-velocity))))
(defun midimacs-midi-flush-note-offs ()
(let ((el))
(loop while (setq el (heap-delete-root midimacs-scheduled-note-offs))
do (midimacs-note-off (nth 1 el) (nth 2 el) (nth 3 el)))))
(defun midimacs-amidicat-read (proc string)
(loop for s in (split-string string "\n")
when (> (length s) 0)
do (midimacs-handle-midi-input
(midimacs-midi-message-set-channel
(midimacs-midi-unserialize s) midimacs-midi-input-channel))))
(defun midimacs-midi-message-set-channel (message channel)
(let ((status (midimacs-midi-message-status message)))
(setf (midimacs-midi-message-status message) (+ status channel))
message))
(defun midimacs-handle-midi-input (message)
(midimacs-midi-execute message)
(cond ((eq midimacs-state 'recording)
(cond ((midimacs-midi-message-is-note-off message)
(midimacs-record-midi-note-off message))
((midimacs-midi-message-is-note-on message)
(midimacs-record-midi-note-on message))))
((and midimacs-is-capturing
(midimacs-buffer-is-code (current-buffer))
(midimacs-midi-message-is-note-on message))
(midimacs-print-message message))))
(defun midimacs-print-message (message)
(insert (midimacs-pitch-to-string (midimacs-midi-message-data1 message)) " "))
(defun midimacs-record-midi-note-on (message)
(let* ((song-time (midimacs-quantized-song-time))
(pitch (midimacs-midi-message-data1 message))
(pitch-string (midimacs-pitch-to-string pitch))
(initial-duration (make-midimacs-time :beat 999))
(start-time (midimacs-score-start-time midimacs-recording-score))
(rel-time (midimacs-time- song-time start-time)))
(when (midimacs-time> rel-time (make-midimacs-time))
(midimacs-add-note-to-score midimacs-recording-score rel-time pitch-string initial-duration))))
(defun midimacs-record-midi-note-off (message)
(let* ((song-time (midimacs-quantized-song-time))
(pitch (midimacs-midi-message-data1 message))
(pitch-string (midimacs-pitch-to-string pitch))
(start-time (midimacs-score-start-time midimacs-recording-score))
(rel-time (midimacs-time- song-time start-time)))
(setf (midimacs-score-notes midimacs-recording-score)
(loop for (tm p d) in (midimacs-score-notes midimacs-recording-score)
if (and (equal p pitch-string) (midimacs-time= d (make-midimacs-time :beat 999)))
collect (let* ((repeat-time (midimacs-time- midimacs-repeat-end midimacs-repeat-start))
(raw-time (midimacs-time- rel-time tm))
(duration (midimacs-time-mod raw-time repeat-time)))
(list tm p duration))
else
collect (list tm p d)))))
(defun* midimacs-play-note (channel pitch-raw duration-raw &optional (velocity nil) (off-velocity 0))
(when (not velocity)
(setq velocity (gethash channel midimacs-channel-default-velocities 100)))
(let ((pitch (midimacs-anything-to-pitch pitch-raw))
(duration (midimacs-anything-to-time duration-raw)))
(midimacs-midi-schedule-note-off (midimacs-time+ midimacs-abs-time duration) channel pitch off-velocity)
(midimacs-note-on channel pitch velocity)))
(defun* midimacs-play-notes (channel pitches-raw duration-raw &optional (velocity nil) (off-velocity 0))
(loop for pitch-raw in pitches-raw
do (midimacs-play-note channel pitch-raw duration-raw velocity off-velocity)))
(defun midimacs-make-scheduled-note-offs-heap ()
(make-heap (lambda (a b) (midimacs-time< (nth 0 a) (nth 0 b)))))
(defun midimacs-started (channel)
(gethash channel midimacs-channel-started-notes))
(defun midimacs-sustained (channel)
(gethash channel midimacs-channel-sustained-notes))
(defun midimacs-stopped (channel)
(gethash channel midimacs-channel-stopped-notes))
(defun* midimacs-note-on (channel pitch &optional (velocity nil))
(when (not velocity)
(setq velocity (gethash channel midimacs-channel-default-velocities 100)))
(let ((started-notes (midimacs-started channel))
(sustained-notes (midimacs-sustained channel)))
(add-to-list 'started-notes pitch)
(puthash channel started-notes midimacs-channel-started-notes)
(add-to-list 'sustained-notes pitch)
(puthash channel sustained-notes midimacs-channel-sustained-notes))
(when (midimacs-valid-channel channel)
(midimacs-midi-execute (midimacs-midi-message-note-on channel pitch velocity))))
(defun* midimacs-note-off (channel pitch &optional (off-velocity 0))
(let ((sustained-notes (midimacs-sustained channel))
(stopped-notes (midimacs-stopped channel)))
(puthash channel (delq pitch sustained-notes) midimacs-channel-sustained-notes)
(add-to-list 'stopped-notes pitch)
(puthash channel stopped-notes midimacs-channel-stopped-notes))
(when (midimacs-valid-channel channel)
(midimacs-midi-execute (midimacs-midi-message-note-off channel pitch off-velocity))))
(defun midimacs-valid-channel (channel)
(and (>= channel 0)
(<= channel 15)))
(defun midimacs-reset-channel-started-stopped-notes ()
(setq midimacs-channel-started-notes (make-hash-table))
(setq midimacs-channel-stopped-notes (make-hash-table)))
(defun midimacs-record-midi ()
(interactive)
(if (eq midimacs-state 'stopped)
(progn
(midimacs-record))
(user-error "Can only start recording from stopped")))
(defun midimacs-all-notes-off (&optional channel)
(interactive)
(if channel
(midimacs-midi-execute (midimacs-midi-message-all-notes-off channel))
(loop for channel below 16
do (midimacs-all-notes-off channel))))
(provide 'midimacs-midi)