annotate implementations/midi/methods.lisp @ 206:1f3873585a5d

Change midifile-import to call event constructors, not make-instance directly.
author Jamie Forth <j.forth@gold.ac.uk>
date Wed, 26 Jan 2011 11:30:28 +0000
parents f59787f1101e
children be3d63b78054
rev   line source
d@34 1 (cl:in-package #:amuse-midi)
d@34 2
d@155 3 (defgeneric (setf midi-velocity) (value event)
d@155 4 (:method (v e) (declare (ignore v)) e))
d@155 5 (defmethod (setf midi-velocity) (value (event midi-pitched-event))
d@155 6 (setf (%midi-pitched-event-velocity event) value)
d@155 7 event)
d@155 8
d@155 9 (defgeneric (setf midi-patch) (value event)
d@155 10 (:method (v e) (declare (ignore v)) e))
d@155 11 (defmethod (setf midi-patch) (value (event midi-pitched-event))
d@155 12 (setf (%midi-pitched-event-patch event) value)
d@155 13 event)
d@155 14
d@155 15 (Defgeneric midi-channel (midi-message)
d@134 16 (:documentation "MIDI channel. Also used for midi output"))
d@34 17 (defmethod midi-channel ((midi-message midi-message))
d@34 18 (%midi-message-channel midi-message))
d@34 19
d@134 20 (defgeneric midi-track (midi-message)
d@134 21 (:documentation "MIDI track. Also used for midi output"))
d@34 22 (defmethod midi-track ((midi-message midi-message))
d@34 23 (%midi-message-track midi-message))
d@34 24
d@134 25 (defgeneric midi-velocity (event)
d@134 26 (:documentation "MIDI velocity. Also used for midi output"))
d@34 27 (defmethod midi-velocity ((event midi-pitched-event))
d@34 28 (%midi-pitched-event-velocity event))
d@34 29 (defmethod midi-velocity ((event midi-percussive-event))
d@34 30 (%midi-percussive-event-velocity event))
d@34 31
d@134 32 (defgeneric midi-patch (event)
d@134 33 (:documentation "MIDI patch (instrumental sound). Also used for
d@134 34 midi output"))
d@34 35 (defmethod midi-patch ((event midi-pitched-event))
d@34 36 (%midi-pitched-event-patch event))
d@34 37
d@134 38 (defgeneric midi-drum-sound (event)
d@134 39 (:documentation "MIDI pitch for unpitched events (usually, drum
d@134 40 sound for drum kits on channel 10, but also for semi-pitched
d@134 41 SFX, etc). Also used for midi output"))
d@34 42 (defmethod midi-drum-sound ((event midi-percussive-event))
d@34 43 (%midi-percussive-event-sound event))
d@34 44
d@34 45 (defmethod time-signatures ((composition midi-composition))
d@34 46 (%midi-time-signatures composition))
d@34 47 (defmethod (setf time-signatures) (sequence (composition midi-composition))
d@34 48 (setf (%midi-time-signatures composition) sequence))
d@34 49 (defmethod tempi ((composition midi-composition))
d@34 50 (%midi-tempi composition))
d@34 51 (defmethod (setf tempi) (sequence (composition midi-composition))
d@34 52 (setf (%midi-tempi composition) sequence))
d@115 53 (defmethod key-signatures ((composition midi-composition))
d@115 54 (%midi-key-signatures composition))
d@115 55 (defmethod (setf key-signatures) (sequence (composition midi-composition))
d@115 56 (setf (%midi-key-signatures composition) sequence))
d@34 57
d@34 58 (defgeneric copy-event (event))
d@34 59 ;; FIXME: This ought to call-next-method and operate on the result,
d@34 60 ;; rather than calling internals from the other package
d@34 61 (defmethod copy-event ((event midi-pitched-event))
j@206 62 (with-slots (channel track (number amuse::number)
j@206 63 (time amuse::time) (interval amuse::interval)
j@206 64 velocity patch) event
j@206 65 (make-midi-pitched-event number velocity patch channel track time
j@206 66 interval)))
j@206 67
d@34 68 (defmethod copy-event ((event midi-percussive-event))
j@206 69 (with-slots (channel track (time amuse::time)
j@206 70 (interval amuse::interval) velocity patch
j@206 71 sound) event
j@206 72 (make-midi-percussive-event sound velocity patch channel track
j@206 73 time interval)))
j@206 74
d@154 75 (defgeneric copy-time-signature (time-signature))
d@154 76 (defmethod copy-time-signature ((time-signature standard-time-signature))
d@154 77 (make-instance (class-of time-signature)
d@154 78 :numerator (time-signature-numerator time-signature)
d@154 79 :denominator (time-signature-denominator time-signature)))
d@154 80 (defmethod copy-time-signature ((time-signature-period standard-time-signature-period))
d@154 81 (let ((sig (call-next-method)))
d@154 82 (setf (timepoint sig) (timepoint time-signature-period)
d@154 83 (duration sig) (duration time-signature-period))
d@154 84 sig))
d@154 85 (defgeneric copy-tempo (tempo))
d@154 86 (defmethod copy-tempo ((tempo standard-tempo))
d@154 87 (make-instance (class-of tempo)
d@154 88 :bpm (bpm tempo)))
d@154 89 (defmethod copy-tempo ((tempo-period standard-tempo-period))
d@154 90 (let ((tp (call-next-method)))
d@154 91 (setf (timepoint tp) (timepoint tempo-period)
d@154 92 (duration tp) (duration tempo-period))
d@154 93 tp))
d@154 94 (defgeneric copy-key-signature (key-signature))
d@154 95 (defmethod copy-key-signature ((key-signature standard-key-signature))
d@154 96 (make-instance (class-of key-signature)
d@154 97 :sharp-count (key-signature-sharps key-signature)
d@154 98 :mode (key-signature-mode key-signature)))
d@154 99 (defmethod copy-key-signature ((key-signature-period standard-key-signature-period))
d@154 100 (let ((sig (call-next-method)))
d@154 101 (setf (timepoint sig) (timepoint key-signature-period)
d@154 102 (duration sig) (duration key-signature-period))
d@154 103 sig))
d@154 104
d@34 105
d@34 106
d@34 107 ;; Allow derived sequences from remove-if, etc. to preserve other slot
d@34 108 ;; info (timesigs, etc)
d@154 109 #+nil
d@34 110 (defmethod sequence:make-sequence-like :around ((o midi-composition) length
d@34 111 &key (initial-element nil iep)
d@34 112 (initial-contents nil icp))
d@34 113 (declare (ignore length initial-element initial-contents))
d@34 114 (let ((result (call-next-method)))
d@34 115 (cond
d@34 116 ((or iep icp)
d@34 117 (setf (timepoint result) (timepoint (elt result 0))
d@34 118 (duration result) (- (timepoint
d@34 119 (loop for e being the elements of result
d@34 120 maximize (cut-off e)))
d@34 121 (timepoint (elt result 0)))))
d@34 122 (t (setf (timepoint result) 0
d@34 123 (duration result) 0)))
d@34 124 (with-slots (time-signatures tempi misc-controllers)
d@34 125 o
d@34 126 (setf (%midi-time-signatures result) time-signatures
d@34 127 (%midi-tempi result) tempi
d@34 128 (%midi-misc-controllers result) misc-controllers))
d@34 129 result))
d@153 130
d@34 131
d@34 132 ;; useful little function
d@34 133
d@34 134 (defun microsecond-per-crotchet-to-bpm (mu-per-c)
d@34 135 (/ 60000000 mu-per-c))
d@34 136
d@34 137 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
d@34 138 ;;
d@34 139 ;; MIDI playback methods
d@34 140
d@34 141 (defmethod get-patch-for-midi ((event midi-pitched-event))
d@34 142 ;; FIXME
d@34 143 (midi-patch event))
d@34 144
d@34 145 (defmethod get-channel-for-midi ((event midi-message))
d@34 146 ;; FIXME 1- ??? I'm only doing this because of the Geerdes
d@34 147 ;; database. Looks like a recipe for disaster. Think should probably
d@34 148 ;; enforce 0-15.
d@34 149 (1- (midi-channel event)))
d@34 150
d@34 151 (defmethod get-velocity-for-midi ((event midi-message))
d@34 152 ;; FIXME: under-exclusive specialisation. Does this matter?
d@34 153 (midi-velocity event))
d@34 154
d@34 155 (defmethod get-pitch-for-midi ((event midi-percussive-event))
d@34 156 (midi-drum-sound event))
d@34 157
d@34 158 (defmethod get-pitch-for-midi ((event midi-pitched-event))
d@41 159 (midi-pitch-number event))
d@41 160
d@41 161 ;; Have avoided percussion vs pitched, as this is more obviously
d@41 162 ;; meaningless.
d@41 163 (defmethod significantly-louderp ((event1 midi-pitched-event) (event2 midi-pitched-event))
d@41 164 (>= (/ (midi-velocity event1)
d@41 165 (midi-velocity event2))
d@41 166 4/3))
d@41 167 (defmethod significantly-louderp ((event1 midi-percussive-event) (event2 midi-percussive-event))
d@41 168 (>= (/ (midi-velocity event1)
d@41 169 (midi-velocity event2))
d@41 170 4/3))
d@41 171 (defmethod substantially-louderp ((event1 midi-pitched-event) (event2 midi-pitched-event))
d@41 172 (>= (/ (midi-velocity event1)
d@41 173 (midi-velocity event2))
d@41 174 2))
d@41 175 (defmethod substantially-louderp ((event1 midi-percussive-event) (event2 midi-percussive-event))
d@41 176 (>= (/ (midi-velocity event1)
d@41 177 (midi-velocity event2))
d@41 178 2))
d@114 179
d@130 180 (defmethod crotchet ((object midi-object))
d@139 181 (make-standard-period 1))
d@157 182
d@157 183 (defmethod monody ((identifier midifile-identifier))
d@157 184 (monody (get-composition identifier)))
d@157 185
d@157 186 ;; TODO: improve this naive first-cut at MONODY for midi files which
d@157 187 ;; simply selects a track which is both monodic (if any monodic tracks
d@157 188 ;; exist) and contains the highest pitch of any monodic track.
d@157 189 (defmethod monody ((c midi-composition))
d@157 190 (flet ((not-overlapping (track)
d@157 191 (let ((result t)
d@157 192 (track (sort (copy-list track) #'amuse:time<)))
d@157 193 (dotimes (i (1- (length track)) result)
d@157 194 (let ((e1 (elt track i))
d@157 195 (e2 (elt track (1+ i))))
d@157 196 (unless (or (before e1 e2) (meets e1 e2))
d@157 197 (setf result nil)))))))
d@157 198 (let ((tracks (make-hash-table))
d@157 199 (result nil)
d@157 200 (max-pitch 0))
d@157 201 (sequence:dosequence (message c)
d@157 202 (let* ((tracknum (amuse-midi:midi-track message))
d@157 203 (track (gethash tracknum tracks)))
d@157 204 (setf (gethash tracknum tracks) (cons message track))))
d@157 205 (maphash #'(lambda (k v)
d@157 206 (declare (ignore k))
d@157 207 (let ((max (apply #'max (mapcar #'midi-pitch-number v))))
d@157 208 (when (and (not-overlapping v) (> max max-pitch))
d@157 209 (setf result (sort v #'amuse:time<)
d@157 210 max-pitch max))))
d@157 211 tracks)
d@157 212 (when result
d@157 213 (let ((monody (make-instance 'midi-monody
d@157 214 :time (amuse:timepoint c)
d@157 215 :interval (amuse:duration c)
d@157 216 :time-signatures (time-signatures c)
d@157 217 :key-signatures (key-signatures c)
d@157 218 :tempi (tempi c)
d@157 219 :controllers (%midi-misc-controllers c))))
d@157 220 (sequence:adjust-sequence monody (length result)
d@157 221 :initial-contents result)
d@157 222 monody)))))
d@157 223
d@157 224 (defmethod trim-enclosing-silence ((composition midi-composition))
d@157 225 (let ((start (timepoint (bar-before (onset (elt composition 0))
d@157 226 composition)))
d@157 227 (end)
d@157 228 (new-sequence) (new-composition))
d@157 229 ;; First attend to the events themselves - copy and slide
d@157 230 (sequence:dosequence (event composition)
d@157 231 (push (copy-event event) new-sequence)
d@157 232 (setf (timepoint (car new-sequence)) (- (timepoint event)
d@157 233 start))
d@157 234 (when (or (not end)
d@157 235 (> (timepoint (cut-off event)) end))
d@157 236 (setf end (timepoint (cut-off event)))))
d@157 237 ;; Make the new composition with slid events
d@157 238 ;; Should work, but doesn't
d@157 239 #+nil
d@157 240 (setf new-composition (sequence:make-sequence-like composition 0))
d@157 241 (setf new-composition (make-instance 'midi-composition
d@157 242 :time 0
d@157 243 :interval (- end start)))
d@157 244 (setf (amuse::%list-slot-sequence-data new-composition)
d@157 245 (reverse new-sequence))
d@157 246 ;; Time-sigs
d@157 247 (let ((sigs))
d@157 248 (dolist (sig (time-signatures composition))
d@157 249 ;; only include if signature affects window
d@157 250 (when (and (> (timepoint (cut-off sig))
d@157 251 start)
d@157 252 (< (timepoint sig)
d@157 253 end))
d@157 254 ;; copy the signature
d@157 255 (push (copy-time-signature sig)
d@157 256 sigs)
d@157 257 ;; adjust the timing
d@157 258 (setf (timepoint (car sigs))
d@157 259 (max 0 (- (timepoint (car sigs)) start))
d@157 260 (duration (car sigs))
d@157 261 (- (min (timepoint (cut-off (car sigs)))
d@157 262 (- end start))
d@157 263 (timepoint (car sigs))))))
d@157 264 (setf (time-signatures new-composition) (reverse sigs)))
d@157 265 (let ((sigs))
d@157 266 (dolist (sig (key-signatures composition))
d@157 267 ;; only include if signature affects window
d@157 268 (when (and (> (timepoint (cut-off sig))
d@157 269 start)
d@157 270 (< (timepoint sig)
d@157 271 end))
d@157 272 ;; copy the signature
d@157 273 (push (copy-key-signature sig)
d@157 274 sigs)
d@157 275 ;; adjust the timing
d@157 276 (setf (timepoint (car sigs))
d@157 277 (max 0 (- (timepoint (car sigs)) start))
d@157 278 (duration (car sigs))
d@157 279 (- (min (timepoint (cut-off (car sigs)))
d@157 280 (- end start))
d@157 281 (timepoint (car sigs))))))
d@157 282 (setf (key-signatures new-composition) (reverse sigs)))
d@157 283 (let ((tempi))
d@157 284 (dolist (tempo (tempi composition))
d@157 285 ;; only include if signature affects window
d@157 286 (when (and (> (timepoint (cut-off tempo))
d@157 287 start)
d@157 288 (< (timepoint tempo)
d@157 289 end))
d@157 290 ;; copy the signature
d@157 291 (push (copy-tempo tempo)
d@157 292 tempi)
d@157 293 ;; adjust the timing
d@157 294 (setf (timepoint (car tempi))
d@157 295 (max 0 (- (timepoint (car tempi)) start))
d@157 296 (duration (car tempi))
d@157 297 (- (min (timepoint (cut-off (car tempi)))
d@157 298 (- end start))
d@157 299 (timepoint (car tempi))))))
d@157 300 (setf (tempi new-composition) (reverse tempi)))
d@157 301 new-composition))
d@157 302
d@157 303
d@157 304 (defgeneric bar-before (moment composition))
d@157 305
d@157 306 (defmethod bar-before (moment (composition midi-composition))
d@157 307 "Returns the moment at which the containing bar begins"
d@157 308 (do ((time-sigs (time-signatures composition) (cdr time-sigs)))
d@157 309 ((null time-sigs) nil)
d@157 310 (let ((bar-period (make-standard-period
d@157 311 (crotchets-in-a-bar (car time-sigs)))))
d@157 312 (when (time> (cut-off (car time-sigs))
d@157 313 moment)
d@157 314 (do ((bar (time+ (onset (car time-sigs)) bar-period)
d@157 315 (time+ bar bar-period))
d@157 316 (prev-bar (onset (car time-sigs))))
d@157 317 ((time> bar moment) (return-from bar-before prev-bar))
d@165 318 (setf prev-bar bar))))))
d@165 319
d@165 320 (defmethod get-applicable-time-signatures ((anchored-period anchored-period)
d@165 321 (composition midi-composition))
d@165 322 (%find-overlapping anchored-period (time-signatures composition)))
d@165 323 (defmethod get-applicable-tempi ((anchored-period anchored-period)
d@165 324 (composition midi-composition))
d@165 325 (%find-overlapping anchored-period (tempi composition)))
d@165 326 (defmethod get-applicable-key-signatures ((anchored-period anchored-period)
d@165 327 (composition midi-composition))
d@165 328 (%find-overlapping anchored-period (key-signatures composition)))
d@165 329
d@165 330 (defun %find-overlapping (period1 period-list)
d@165 331 (let ((result-list))
d@165 332 (dolist (period2 period-list result-list)
d@165 333 (cond
d@165 334 ((time>= period2 (cut-off period1))
d@165 335 (return-from %find-overlapping (reverse result-list)))
d@165 336 ((time> (cut-off period2) period1)
j@206 337 (push period2 result-list))))))