annotate implementations/midi/midifile-import.lisp @ 201:4e0a5c7026ca

Midi importing functionality committer: David Lewis <d.lewis@gold.ac.uk>
author David Lewis <david@localhost.localdomain>
date Wed, 08 Sep 2010 13:06:36 +0100
parents 4a0e15e2829a
children 10d47e78a53d
rev   line source
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)))))