d@64
|
1 ;; Make midifiles from basic amuse objects methods here can be
|
d@64
|
2 ;; overridden for more specific types
|
d@64
|
3 ;;
|
d@64
|
4
|
d@64
|
5 ;; FIXME: Need to push some structures from geerdes to make this work.
|
d@64
|
6
|
d@64
|
7 (in-package #:amuse-tools)
|
d@64
|
8
|
d@64
|
9 (defgeneric play (music)
|
d@134
|
10 (:method (m) (play-midifile (make-midi m)))
|
d@134
|
11 (:documentation "Plays using timidity, unless mac os is
|
d@134
|
12 present, in which case open (=QuickTime) is used. CoreAudio
|
d@134
|
13 code exists, but is awkward to maintain."))
|
d@64
|
14 (defmethod play ((music composition))
|
d@64
|
15 (play-midifile (make-midi music)))
|
d@64
|
16
|
d@134
|
17 (defun write-midi (music pathname)
|
d@134
|
18 "Exports music as midi file."
|
d@134
|
19 (midi:write-midi-file (make-midi music) pathname))
|
d@134
|
20
|
d@64
|
21 (defun play-midifile (midifile)
|
d@64
|
22 ;; coremidi is easy as an alternative, but we'll probably want midi
|
d@64
|
23 ;; file export anyway, so it makes some sense to focus our efforts
|
d@64
|
24 ;; on this first. That said, is there a CoreAudio midi file player
|
d@64
|
25 ;; routine?
|
d@64
|
26 (midi:write-midi-file midifile "tmp.mid")
|
j@188
|
27 #+darwin
|
j@188
|
28 (sb-ext:run-program "open" '("tmp.mid") :search t)
|
j@188
|
29 #+linux
|
j@188
|
30 (sb-ext:run-program "xdg-open" '("tmp.mid") :search t))
|
d@64
|
31
|
d@64
|
32 (defgeneric make-midi (sequence))
|
d@64
|
33 (defmethod make-midi ((sequence sequence))
|
d@64
|
34 ;; Make a midifile object. Collects global midi messages (that
|
d@64
|
35 ;; require a sequence) and event-based messages (that don't).
|
d@64
|
36 ;; FIXME: Something about this strikes me as very stupid. Must
|
d@64
|
37 ;; revisit
|
d@64
|
38 ;; FIXME: Only making type 0. Is this a problem?
|
d@64
|
39 (let* ((events (event-sequence-messages sequence))
|
d@64
|
40 (globals (global-messages sequence))
|
d@64
|
41 (patches (patch-messages sequence)))
|
d@64
|
42 (make-midifile-from-messages (nconc events globals patches)
|
d@64
|
43 :type 0)))
|
d@64
|
44
|
d@64
|
45 (defun make-midifile-from-messages (events &key (type 0))
|
d@64
|
46 ;; FIXME: clearly broken if type 1
|
d@64
|
47 ;; First have to get rid of all fractional times and choose a
|
d@64
|
48 ;; timebase
|
d@64
|
49 (let* ((timebase (apply #'lcm (mapcar #'(lambda (x)
|
d@64
|
50 (denominator
|
d@64
|
51 (midi:message-time x)))
|
d@64
|
52 events))))
|
d@64
|
53 (when (< timebase 4)
|
d@64
|
54 (setf timebase (* 4 timebase)))
|
d@64
|
55 (loop for e in events
|
d@64
|
56 do (setf (midi:message-time e) (* timebase
|
d@64
|
57 (midi:message-time e))))
|
d@64
|
58 (make-instance 'midi:midifile
|
d@64
|
59 :format type
|
d@64
|
60 :division timebase
|
d@64
|
61 :tracks (list (sort-midi-messages-for-output events)))))
|
d@64
|
62
|
d@64
|
63 (defun sort-midi-messages-for-output (messages)
|
d@64
|
64 (sort messages #'(lambda (x y) (or (< (midi:message-time x)
|
d@64
|
65 (midi:message-time y))
|
d@64
|
66 (and (= (midi:message-time x)
|
d@64
|
67 (midi:message-time y))
|
d@64
|
68 (< (midi::message-status x)
|
d@64
|
69 (midi::message-status y)))))))
|
d@64
|
70
|
d@64
|
71 (defun event-sequence-messages (sequence)
|
d@64
|
72 (let ((midinotes))
|
d@64
|
73 (sequence:dosequence (event sequence midinotes)
|
d@64
|
74 (let ((messages (event-messages event)))
|
d@64
|
75 (dolist (message messages)
|
d@64
|
76 (push message midinotes))))))
|
d@64
|
77
|
d@64
|
78 (defun patch-messages (sequence)
|
d@64
|
79 (let ((patches (make-array 16 :initial-element nil))
|
d@64
|
80 (patch-list)
|
d@64
|
81 (channel 0)
|
d@64
|
82 (patch 0))
|
d@64
|
83 (sequence:dosequence (event sequence patch-list)
|
d@64
|
84 (setf channel (get-channel-for-midi event)
|
d@64
|
85 patch (get-patch-for-midi event))
|
d@64
|
86 (when (or (not (aref patches channel))
|
d@64
|
87 (not (= (aref patches channel)
|
d@64
|
88 patch)))
|
d@64
|
89 (push (make-instance 'midi:program-change-message
|
d@64
|
90 :program patch
|
d@64
|
91 :time (timepoint event)
|
d@64
|
92 :status (+ channel 192))
|
d@64
|
93 patch-list)
|
d@64
|
94 (setf (aref patches channel) patch)))))
|
j@189
|
95
|
j@189
|
96 (defparameter *default-tempo* 80)
|
d@183
|
97 (defgeneric default-tempo-for-midi (anchored-period)
|
d@183
|
98 (:method ((o standard-anchored-period))
|
d@183
|
99 (make-standard-tempo-period *default-tempo* (timepoint o) (duration o))))
|
d@64
|
100
|
d@64
|
101 (defgeneric global-messages (sequence)
|
d@64
|
102 (:method (s) (declare (ignore s)) nil))
|
d@64
|
103 (defmethod global-messages ((sequence composition))
|
d@64
|
104 ;; FIXME: missing plenty of other messages
|
d@64
|
105 ;; FIXME: messy
|
d@64
|
106 (let ((tempi (tempi sequence))
|
d@64
|
107 (temp)
|
d@64
|
108 (time-sigs (time-signatures sequence))
|
d@64
|
109 (events))
|
d@183
|
110 (if tempi
|
d@183
|
111 (dolist (tempo tempi)
|
d@183
|
112 (setf temp (tempo-message tempo))
|
d@183
|
113 (when temp
|
d@183
|
114 (push temp events)))
|
d@183
|
115 (push (tempo-message (default-tempo-for-midi sequence))
|
d@183
|
116 events))
|
d@64
|
117 (dolist (time-sig time-sigs events)
|
d@64
|
118 (setf temp (time-sig-message time-sig))
|
d@64
|
119 (when temp
|
d@64
|
120 (push temp events)))))
|
d@64
|
121
|
d@64
|
122 (defgeneric tempo-message (tempo)
|
d@64
|
123 (:method (tp)
|
d@64
|
124 (progn
|
d@64
|
125 (let ((speed (make-instance 'midi:tempo-message
|
d@64
|
126 :time (timepoint tp)
|
d@64
|
127 :status 255)))
|
d@64
|
128 (setf (slot-value speed 'midi::tempo) (microseconds-per-crotchet tp))
|
d@64
|
129 speed))))
|
d@64
|
130
|
d@64
|
131 (defgeneric time-sig-message (time-sig)
|
d@64
|
132 (:method (ts) (declare (ignore ts)) nil))
|
d@64
|
133
|
d@64
|
134 (defgeneric event-messages (event)
|
d@64
|
135 (:method (e) (declare (ignore e)) nil))
|
d@136
|
136 (defmethod event-messages ((event standard-chromatic-pitched-event))
|
d@64
|
137 (list (make-instance 'midi:note-on-message
|
d@64
|
138 :status (+ (get-channel-for-midi event) 144)
|
d@64
|
139 :key (midi-pitch-number event)
|
d@64
|
140 :velocity (get-velocity-for-midi event)
|
d@64
|
141 :time (timepoint event))
|
d@64
|
142 (make-instance 'midi:note-off-message
|
d@64
|
143 :status (+ (get-channel-for-midi event) 128)
|
d@64
|
144 :key (midi-pitch-number event)
|
d@64
|
145 :velocity (get-velocity-for-midi event)
|
d@64
|
146 :time (timepoint (cut-off event)))))
|
d@64
|
147
|
d@136
|
148 (defmethod event-messages ((event standard-percussive-event))
|
d@64
|
149 (list (make-instance 'midi:note-on-message
|
d@64
|
150 :status 153
|
d@64
|
151 :key (get-pitch-for-midi event)
|
d@64
|
152 :velocity (get-velocity-for-midi event)
|
d@64
|
153 :time (timepoint event))
|
d@64
|
154 (make-instance 'midi:note-off-message
|
d@64
|
155 :status 137
|
d@64
|
156 :key (get-pitch-for-midi event)
|
d@64
|
157 :velocity (get-velocity-for-midi event)
|
d@64
|
158 :time (timepoint (cut-off event)))))
|
d@64
|
159
|
d@64
|
160 (defgeneric get-pitch-for-midi (event))
|
d@64
|
161 (defgeneric get-velocity-for-midi (event)
|
d@64
|
162 (:method (e) (declare (ignore e)) 100))
|
d@64
|
163 (defgeneric get-patch-for-midi (event)
|
d@64
|
164 (:method (e) (declare (ignore e)) 0))
|
d@64
|
165 (defgeneric get-channel-for-midi (event)
|
d@64
|
166 (:method (e) (declare (ignore e)) 0))
|