annotate implementations/midi/midifile-import.lisp @ 36:ad321ce17e3e

Moving some functionality from specialised geerdes area. Also added mcsv output darcs-hash:20070511120916-f76cc-d6f1b566eea7115c5de1d3aad285c84b304730b7.gz
author David Lewis <d.lewis@gold.ac.uk>
date Fri, 11 May 2007 13:09:16 +0100
parents 1d757c33e00e
children cf198383852d
rev   line source
d@35 1 (cl:in-package #:amuse-midi)
d@35 2
d@35 3 (defclass midifile-identifier (identifier)
d@35 4 ((pathname :initarg :path
d@35 5 :reader midifile-identifier-pathname
d@35 6 :initform 'nil)))
d@35 7
d@35 8 (defun midifile-id (pathname)
d@35 9 (make-instance 'midifile-identifier :path pathname))
d@35 10
d@35 11 (defmethod get-composition ((identifier midifile-identifier))
d@35 12 (%initialise-midifile-composition (midi:read-midi-file
d@35 13 (midifile-identifier-pathname identifier))))
d@35 14
d@35 15 (defun %initialise-midifile-composition (midifile)
d@35 16 ;; Takes a midifile object (from the "MIDI" package)
d@35 17 ;; and returns an amuse midi object
d@35 18 ;; FIXME: gets it wrong if patch changes in mid-note
d@35 19 ;; FIXME: assumes controllers are global in scope and location
d@35 20 (let ((tracks (midi:midifile-tracks midifile))
d@35 21 (division (midi:midifile-division midifile))
d@35 22 (notes) (time-sigs) (tempi) (misses 0) (track-no -1) (last-time 0))
d@35 23 (dolist (track tracks)
d@35 24 (incf track-no)
d@35 25 (setf track (sort (copy-seq track)
d@35 26 #'(lambda (x y)
d@35 27 (or (< (midi:message-time x)
d@35 28 (midi:message-time y))
d@35 29 (and (= (midi:message-time x)
d@35 30 (midi:message-time y))
d@35 31 (typep x 'midi:note-off-message))))))
d@36 32 (let ((ons (make-array '(16 128) :initial-element nil))
d@35 33 (offs)
d@36 34 (patches (make-array 16 :initial-element 0)))
d@35 35 (dolist (event track)
d@35 36 (when (> (midi:message-time event) last-time)
d@35 37 (setf last-time (midi:message-time event)))
d@35 38 (cond
d@35 39 ((or (typep event 'midi:note-off-message)
d@35 40 (and (typep event 'midi:note-on-message)
d@35 41 (= (midi:message-velocity event) 0)))
d@35 42 (let ((pitch (midi:message-key event))
d@36 43 (channel (midi:message-channel event))
d@35 44 (t-off (midi:message-time event)))
d@35 45 (if (aref ons channel pitch)
d@35 46 (push (make-event-from-on-off-pair (aref ons channel pitch)
d@35 47 t-off
d@35 48 division
d@35 49 track-no
d@35 50 (aref patches channel))
d@35 51 notes)
d@35 52 ;; if there's no matching on, wait until the beat
d@35 53 ;; is done.
d@35 54 (push event offs))))
d@35 55 ((typep event 'midi:note-on-message)
d@35 56 (let ((pitch (midi:message-key event))
d@36 57 (channel (midi:message-channel event))
d@35 58 (t-off (midi:message-time event)))
d@35 59 (when (aref ons channel pitch)
d@35 60 ;; there's a note already sounding. End it.
d@35 61 (push (make-event-from-on-off-pair (aref ons channel pitch)
d@35 62 t-off
d@35 63 division
d@35 64 track-no
d@35 65 (aref patches channel))
d@35 66 notes))
d@35 67 (setf (aref ons channel pitch) event)))
d@35 68 ((typep event 'midi:time-signature-message)
d@35 69 ;; FIXME: Should I make a midi version of this object,
d@35 70 ;; with track/channel?
d@35 71 (when time-sigs
d@35 72 (setf (duration (car time-sigs))
d@35 73 (- (/ (midi:message-time event)
d@35 74 division)
d@35 75 (timepoint (car time-sigs)))))
d@35 76 (push (make-instance 'basic-time-signature
d@35 77 :time (/ (midi:message-time event)
d@35 78 division)
d@35 79 :numerator (midi:message-numerator event)
d@36 80 :denominator (expt 2 (midi:message-denominator event)))
d@35 81 time-sigs))
d@35 82 ((typep event 'midi:tempo-message)
d@35 83 (when tempi
d@35 84 (setf (duration (car tempi))
d@35 85 (- (/ (midi:message-time event)
d@35 86 division)
d@35 87 (timepoint (car tempi)))))
d@35 88 (push (make-instance 'tempo
d@35 89 :time (/ (midi:message-time event)
d@35 90 division)
d@35 91 :bpm (microsecond-per-crotchet-to-bpm (midi:message-tempo event)))
d@35 92 tempi))
d@35 93 ((typep event 'midi:program-change-message)
d@36 94 (setf (aref patches (midi:message-channel event))
d@35 95 (midi:message-program event)))
d@35 96 (t (incf misses))))))
d@35 97 (when tempi
d@35 98 (setf (duration (car tempi)) (- (/ last-time division) (timepoint (car tempi)))))
d@35 99 (when time-sigs
d@35 100 (setf (duration (car time-sigs)) (- (/ last-time division) (timepoint (car time-sigs)))))
d@35 101 ;; make a midi object from notes, etc.
d@35 102 (let ((composition (make-instance 'midi-composition
d@35 103 :time 0
d@35 104 :interval (/ last-time division)
d@36 105 :time-signatures (if time-sigs
d@36 106 (sort time-sigs #'time<)
d@36 107 (list (make-instance 'basic-time-signature
d@36 108 :time 0
d@36 109 :interval (/ last-time division)
d@36 110 :numerator 4
d@36 111 :denominator 4)))
d@35 112 :tempi (sort tempi #'time<))))
d@35 113 (sequence:adjust-sequence composition
d@35 114 (length notes)
d@35 115 :initial-contents (sort notes #'time<)))))
d@35 116
d@35 117 (defun make-event-from-on-off-pair (note-on cut-off divisions track patch)
d@35 118 (cond
d@35 119 ((or (= (midi:message-channel note-on) 9)
d@35 120 (> patch 111))
d@35 121 ;; percussive
d@35 122 (make-instance 'midi-percussive-event
d@35 123 :channel (1+ (midi:message-channel note-on))
d@35 124 :track track
d@35 125 :time (/ (midi:message-time note-on) divisions)
d@35 126 :interval (/ (- cut-off (midi:message-time note-on))
d@35 127 divisions)
d@35 128 :velocity (midi:message-velocity note-on)
d@35 129 :patch patch
d@35 130 :sound (midi:message-key note-on)))
d@35 131 (t
d@35 132 ;; pitched
d@35 133 (make-instance 'midi-pitched-event
d@35 134 :channel (1+ (midi:message-channel note-on))
d@35 135 :track track
d@35 136 :time (/ (midi:message-time note-on) divisions)
d@35 137 :interval (/ (- cut-off (midi:message-time note-on))
d@35 138 divisions)
d@35 139 :velocity (midi:message-velocity note-on)
d@35 140 :patch patch
d@35 141 :number (midi:message-key note-on)))))