d@35
|
1 (cl:in-package #:amuse-midi)
|
d@35
|
2
|
david@201
|
3 (defparameter *default-tempo* 120)
|
david@201
|
4
|
d@169
|
5 (defclass midifile-identifier (composition-identifier midi-object)
|
d@35
|
6 ((pathname :initarg :path
|
d@35
|
7 :reader midifile-identifier-pathname
|
d@134
|
8 :initform 'nil))
|
d@134
|
9 (:documentation "Identifier for MIDI files, containing pathname
|
d@134
|
10 information"))
|
d@35
|
11
|
d@35
|
12 (defun midifile-id (pathname)
|
d@134
|
13 "Creates an identifier for MIDI files, based on a pathname"
|
d@35
|
14 (make-instance 'midifile-identifier :path pathname))
|
d@35
|
15
|
d@35
|
16 (defmethod get-composition ((identifier midifile-identifier))
|
d@35
|
17 (%initialise-midifile-composition (midi:read-midi-file
|
d@35
|
18 (midifile-identifier-pathname identifier))))
|
d@35
|
19
|
d@35
|
20 (defun %initialise-midifile-composition (midifile)
|
d@35
|
21 ;; Takes a midifile object (from the "MIDI" package)
|
d@35
|
22 ;; and returns an amuse midi object
|
d@35
|
23 ;; FIXME: gets it wrong if patch changes in mid-note
|
d@35
|
24 ;; FIXME: assumes controllers are global in scope and location
|
d@35
|
25 (let ((tracks (midi:midifile-tracks midifile))
|
d@35
|
26 (division (midi:midifile-division midifile))
|
d@115
|
27 (notes) (time-sigs) (key-sigs) (tempi) (misses 0)
|
d@115
|
28 (track-no -1) (last-time 0))
|
david@201
|
29 (when *default-tempo*
|
david@201
|
30 (push (make-instance 'standard-tempo-period
|
david@201
|
31 :time 0
|
david@201
|
32 :bpm *default-tempo*) tempi))
|
d@35
|
33 (dolist (track tracks)
|
d@35
|
34 (incf track-no)
|
d@35
|
35 (setf track (sort (copy-seq track)
|
d@35
|
36 #'(lambda (x y)
|
d@35
|
37 (or (< (midi:message-time x)
|
d@35
|
38 (midi:message-time y))
|
d@35
|
39 (and (= (midi:message-time x)
|
d@35
|
40 (midi:message-time y))
|
d@35
|
41 (typep x 'midi:note-off-message))))))
|
d@36
|
42 (let ((ons (make-array '(16 128) :initial-element nil))
|
d@35
|
43 (offs)
|
d@36
|
44 (patches (make-array 16 :initial-element 0)))
|
d@35
|
45 (dolist (event track)
|
d@35
|
46 (when (> (midi:message-time event) last-time)
|
d@35
|
47 (setf last-time (midi:message-time event)))
|
d@35
|
48 (cond
|
d@35
|
49 ((or (typep event 'midi:note-off-message)
|
d@35
|
50 (and (typep event 'midi:note-on-message)
|
d@35
|
51 (= (midi:message-velocity event) 0)))
|
d@35
|
52 (let ((pitch (midi:message-key event))
|
d@36
|
53 (channel (midi:message-channel event))
|
d@35
|
54 (t-off (midi:message-time event)))
|
d@35
|
55 (if (aref ons channel pitch)
|
m@145
|
56 (progn
|
m@145
|
57 (push (make-event-from-on-off-pair (aref ons channel pitch)
|
m@145
|
58 t-off
|
m@145
|
59 division
|
m@145
|
60 track-no
|
m@145
|
61 (aref patches channel))
|
m@145
|
62 notes)
|
m@145
|
63 (setf (aref ons channel pitch) nil))
|
d@35
|
64 ;; if there's no matching on, wait until the beat
|
d@35
|
65 ;; is done.
|
d@35
|
66 (push event offs))))
|
d@35
|
67 ((typep event 'midi:note-on-message)
|
d@35
|
68 (let ((pitch (midi:message-key event))
|
d@36
|
69 (channel (midi:message-channel event))
|
d@35
|
70 (t-off (midi:message-time event)))
|
d@35
|
71 (when (aref ons channel pitch)
|
d@35
|
72 ;; there's a note already sounding. End it.
|
d@35
|
73 (push (make-event-from-on-off-pair (aref ons channel pitch)
|
d@35
|
74 t-off
|
d@35
|
75 division
|
d@35
|
76 track-no
|
d@35
|
77 (aref patches channel))
|
d@35
|
78 notes))
|
d@35
|
79 (setf (aref ons channel pitch) event)))
|
d@35
|
80 ((typep event 'midi:time-signature-message)
|
d@35
|
81 ;; FIXME: Should I make a midi version of this object,
|
d@35
|
82 ;; with track/channel?
|
d@35
|
83 (when time-sigs
|
d@35
|
84 (setf (duration (car time-sigs))
|
d@35
|
85 (- (/ (midi:message-time event)
|
d@35
|
86 division)
|
d@35
|
87 (timepoint (car time-sigs)))))
|
d@136
|
88 (push (make-instance 'standard-time-signature-period
|
d@35
|
89 :time (/ (midi:message-time event)
|
d@35
|
90 division)
|
d@35
|
91 :numerator (midi:message-numerator event)
|
d@36
|
92 :denominator (expt 2 (midi:message-denominator event)))
|
d@35
|
93 time-sigs))
|
d@115
|
94 ((typep event 'midi:key-signature-message)
|
d@115
|
95 ;; FIXME: Should I make a midi version of this object,
|
d@115
|
96 ;; with track/channel? [probably, yes]
|
d@115
|
97 (when key-sigs
|
d@115
|
98 (setf (duration (car time-sigs))
|
d@115
|
99 (- (/ (midi:message-time event)
|
d@115
|
100 division)
|
d@115
|
101 (timepoint (car time-sigs)))))
|
d@136
|
102 (push (make-instance 'midi-key-signature-period
|
d@115
|
103 :time (/ (midi:message-time event)
|
d@115
|
104 division)
|
d@119
|
105 :sharp-count (midi:message-sf event)
|
d@119
|
106 :mode (midi:message-mi event))
|
d@115
|
107 key-sigs))
|
d@35
|
108 ((typep event 'midi:tempo-message)
|
d@35
|
109 (when tempi
|
david@201
|
110 (if (= (midi:message-time event) 0)
|
david@201
|
111 ;; to allow for default tempo kludge. Nightingale
|
david@201
|
112 ;; seems to happily export midi with no initial
|
david@201
|
113 ;; tempo, but tempo changes later. Making this
|
david@201
|
114 ;; uncertainty last beyond import could prove
|
david@201
|
115 ;; confusing (though is probably the `right'
|
david@201
|
116 ;; answer)
|
david@201
|
117 (setf tempi nil)
|
david@201
|
118 (setf (duration (car tempi))
|
david@201
|
119 (- (/ (midi:message-time event)
|
david@201
|
120 division)
|
david@201
|
121 (timepoint (car tempi))))))
|
d@136
|
122 (push (make-instance 'standard-tempo-period
|
d@35
|
123 :time (/ (midi:message-time event)
|
d@35
|
124 division)
|
d@35
|
125 :bpm (microsecond-per-crotchet-to-bpm (midi:message-tempo event)))
|
d@35
|
126 tempi))
|
d@35
|
127 ((typep event 'midi:program-change-message)
|
d@36
|
128 (setf (aref patches (midi:message-channel event))
|
d@35
|
129 (midi:message-program event)))
|
d@35
|
130 (t (incf misses))))))
|
d@35
|
131 (when tempi
|
d@35
|
132 (setf (duration (car tempi)) (- (/ last-time division) (timepoint (car tempi)))))
|
d@35
|
133 (when time-sigs
|
d@35
|
134 (setf (duration (car time-sigs)) (- (/ last-time division) (timepoint (car time-sigs)))))
|
d@115
|
135 (when key-sigs
|
d@115
|
136 (setf (duration (car key-sigs)) (- (/ last-time division) (timepoint (car key-sigs)))))
|
d@35
|
137 ;; make a midi object from notes, etc.
|
d@35
|
138 (let ((composition (make-instance 'midi-composition
|
d@35
|
139 :time 0
|
d@35
|
140 :interval (/ last-time division)
|
d@36
|
141 :time-signatures (if time-sigs
|
d@36
|
142 (sort time-sigs #'time<)
|
d@136
|
143 (list (make-standard-time-signature-period
|
d@136
|
144 4 4 0 (/ last-time division))))
|
d@115
|
145 :tempi (sort tempi #'time<)
|
d@119
|
146 :key-signatures (sort key-sigs #'time<))))
|
d@35
|
147 (sequence:adjust-sequence composition
|
d@35
|
148 (length notes)
|
d@35
|
149 :initial-contents (sort notes #'time<)))))
|
d@35
|
150
|
d@35
|
151 (defun make-event-from-on-off-pair (note-on cut-off divisions track patch)
|
d@35
|
152 (cond
|
d@35
|
153 ((or (= (midi:message-channel note-on) 9)
|
d@35
|
154 (> patch 111))
|
d@35
|
155 ;; percussive
|
d@35
|
156 (make-instance 'midi-percussive-event
|
d@35
|
157 :channel (1+ (midi:message-channel note-on))
|
d@35
|
158 :track track
|
d@35
|
159 :time (/ (midi:message-time note-on) divisions)
|
d@35
|
160 :interval (/ (- cut-off (midi:message-time note-on))
|
d@35
|
161 divisions)
|
d@35
|
162 :velocity (midi:message-velocity note-on)
|
d@35
|
163 :patch patch
|
d@35
|
164 :sound (midi:message-key note-on)))
|
d@35
|
165 (t
|
d@35
|
166 ;; pitched
|
d@35
|
167 (make-instance 'midi-pitched-event
|
d@35
|
168 :channel (1+ (midi:message-channel note-on))
|
d@35
|
169 :track track
|
d@35
|
170 :time (/ (midi:message-time note-on) divisions)
|
d@35
|
171 :interval (/ (- cut-off (midi:message-time note-on))
|
d@35
|
172 divisions)
|
d@35
|
173 :velocity (midi:message-velocity note-on)
|
d@35
|
174 :patch patch
|
m@145
|
175 :number (midi:message-key note-on)))))
|