annotate tools/midi-output.lisp @ 136:fd85f52d9f9d

Class revolution * PITCH-DESIGNATOR -> PITCH (PITCH removed) * MOMENT-DESIGNATOR -> MOMENT , MOMENT -> STANDARD-MOMENT * PERIOD-DESIGNATOR -> PERIOD , PERIOD -> STANDARD-PERIOD * ANCHORED-PERIOD-DESIGNATOR -> ANCHORED-PERIOD , ANCHORED-PERIOD -> STANDARD-ANCHORED-PERIOD * FLOATING-PERIOD removed * TIME-SIGNATURE-DESIGNATOR -> TIME-SIGNATURE & TIME-SIGNATURE-PERIOD * TIME-SIGNATURE -> STANDARD-TIME-SIGNATURE & STANDARD-TIME-SIGNATURE-PERIOD * KEY-SIGNATURE-DESIGNATOR -> KEY-SIGNATURE (& ...-PERIOD) * KEY-SIGNATURE -> STANDARD-KEY-SIGNATURE (& ...-PERIOD) * TEMPO now abstract (& TEMPO-PERIOD) * STANDARD-TEMPO AND STANDARD-TEMPO-PERIOD * COMPOSITION, CONSTITUENT & TIME-ORDERED-CONSTITUENT all have STANDARD- forms make-x methods and specialisers changes appropriately darcs-hash:20070831142943-f76cc-7be0d08963de06d87b36e4922076287d565c7ee2.gz
author David Lewis <d.lewis@gold.ac.uk>
date Fri, 31 Aug 2007 15:29:43 +0100
parents 5e362d998f29
children 5b2d0e5a99f1
rev   line source
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")
d@64 27 #+darwin
d@64 28 (when (sb-impl::find-executable-in-search-path "open")
d@64 29 (asdf:run-shell-command "open tmp.mid")
d@64 30 (return-from play-midifile T))
d@64 31 (when (sb-impl::find-executable-in-search-path "timidity")
d@64 32 (asdf:run-shell-command "timidity tmp.mid")
d@64 33 (return-from play-midifile T)))
d@64 34
d@64 35 (defgeneric make-midi (sequence))
d@64 36 (defmethod make-midi ((sequence sequence))
d@64 37 ;; Make a midifile object. Collects global midi messages (that
d@64 38 ;; require a sequence) and event-based messages (that don't).
d@64 39 ;; FIXME: Something about this strikes me as very stupid. Must
d@64 40 ;; revisit
d@64 41 ;; FIXME: Only making type 0. Is this a problem?
d@64 42 (let* ((events (event-sequence-messages sequence))
d@64 43 (globals (global-messages sequence))
d@64 44 (patches (patch-messages sequence)))
d@64 45 (make-midifile-from-messages (nconc events globals patches)
d@64 46 :type 0)))
d@64 47
d@64 48 (defun make-midifile-from-messages (events &key (type 0))
d@64 49 ;; FIXME: clearly broken if type 1
d@64 50 ;; First have to get rid of all fractional times and choose a
d@64 51 ;; timebase
d@64 52 (let* ((timebase (apply #'lcm (mapcar #'(lambda (x)
d@64 53 (denominator
d@64 54 (midi:message-time x)))
d@64 55 events))))
d@64 56 (when (< timebase 4)
d@64 57 (setf timebase (* 4 timebase)))
d@64 58 (loop for e in events
d@64 59 do (setf (midi:message-time e) (* timebase
d@64 60 (midi:message-time e))))
d@64 61 (make-instance 'midi:midifile
d@64 62 :format type
d@64 63 :division timebase
d@64 64 :tracks (list (sort-midi-messages-for-output events)))))
d@64 65
d@64 66 (defun sort-midi-messages-for-output (messages)
d@64 67 (sort messages #'(lambda (x y) (or (< (midi:message-time x)
d@64 68 (midi:message-time y))
d@64 69 (and (= (midi:message-time x)
d@64 70 (midi:message-time y))
d@64 71 (< (midi::message-status x)
d@64 72 (midi::message-status y)))))))
d@64 73
d@64 74 (defun event-sequence-messages (sequence)
d@64 75 (let ((midinotes))
d@64 76 (sequence:dosequence (event sequence midinotes)
d@64 77 (let ((messages (event-messages event)))
d@64 78 (dolist (message messages)
d@64 79 (push message midinotes))))))
d@64 80
d@64 81 (defun patch-messages (sequence)
d@64 82 (let ((patches (make-array 16 :initial-element nil))
d@64 83 (patch-list)
d@64 84 (channel 0)
d@64 85 (patch 0))
d@64 86 (sequence:dosequence (event sequence patch-list)
d@64 87 (setf channel (get-channel-for-midi event)
d@64 88 patch (get-patch-for-midi event))
d@64 89 (when (or (not (aref patches channel))
d@64 90 (not (= (aref patches channel)
d@64 91 patch)))
d@64 92 (push (make-instance 'midi:program-change-message
d@64 93 :program patch
d@64 94 :time (timepoint event)
d@64 95 :status (+ channel 192))
d@64 96 patch-list)
d@64 97 (setf (aref patches channel) patch)))))
d@64 98
d@64 99
d@64 100 (defgeneric global-messages (sequence)
d@64 101 (:method (s) (declare (ignore s)) nil))
d@64 102 (defmethod global-messages ((sequence composition))
d@64 103 ;; FIXME: missing plenty of other messages
d@64 104 ;; FIXME: messy
d@64 105 (let ((tempi (tempi sequence))
d@64 106 (temp)
d@64 107 (time-sigs (time-signatures sequence))
d@64 108 (events))
d@64 109 (dolist (tempo tempi)
d@64 110 (setf temp (tempo-message tempo))
d@64 111 (when temp
d@64 112 (push temp events)))
d@64 113 (dolist (time-sig time-sigs events)
d@64 114 (setf temp (time-sig-message time-sig))
d@64 115 (when temp
d@64 116 (push temp events)))))
d@64 117
d@64 118 (defgeneric tempo-message (tempo)
d@64 119 (:method (tp)
d@64 120 (progn
d@64 121 (let ((speed (make-instance 'midi:tempo-message
d@64 122 :time (timepoint tp)
d@64 123 :status 255)))
d@64 124 (setf (slot-value speed 'midi::tempo) (microseconds-per-crotchet tp))
d@64 125 speed))))
d@64 126
d@64 127 (defgeneric time-sig-message (time-sig)
d@64 128 (:method (ts) (declare (ignore ts)) nil))
d@64 129
d@64 130 (defgeneric event-messages (event)
d@64 131 (:method (e) (declare (ignore e)) nil))
d@136 132 (defmethod event-messages ((event standard-chromatic-pitched-event))
d@64 133 (list (make-instance 'midi:note-on-message
d@64 134 :status (+ (get-channel-for-midi event) 144)
d@64 135 :key (midi-pitch-number event)
d@64 136 :velocity (get-velocity-for-midi event)
d@64 137 :time (timepoint event))
d@64 138 (make-instance 'midi:note-off-message
d@64 139 :status (+ (get-channel-for-midi event) 128)
d@64 140 :key (midi-pitch-number event)
d@64 141 :velocity (get-velocity-for-midi event)
d@64 142 :time (timepoint (cut-off event)))))
d@64 143
d@136 144 (defmethod event-messages ((event standard-percussive-event))
d@64 145 (list (make-instance 'midi:note-on-message
d@64 146 :status 153
d@64 147 :key (get-pitch-for-midi event)
d@64 148 :velocity (get-velocity-for-midi event)
d@64 149 :time (timepoint event))
d@64 150 (make-instance 'midi:note-off-message
d@64 151 :status 137
d@64 152 :key (get-pitch-for-midi event)
d@64 153 :velocity (get-velocity-for-midi event)
d@64 154 :time (timepoint (cut-off event)))))
d@64 155
d@64 156 (defgeneric get-pitch-for-midi (event))
d@64 157 (defgeneric get-velocity-for-midi (event)
d@64 158 (:method (e) (declare (ignore e)) 100))
d@64 159 (defgeneric get-patch-for-midi (event)
d@64 160 (:method (e) (declare (ignore e)) 0))
d@64 161 (defgeneric get-channel-for-midi (event)
d@64 162 (:method (e) (declare (ignore e)) 0))