annotate utils/midi-output.lisp @ 33:d1010755f507

Large upload of local changes. Many additions, such as harmony and piece-level objects darcs-hash:20070413100909-f76cc-a8aa8dfc07f438dc0c1a7c45cee7ace2ecc1e6a5.gz
author David Lewis <d.lewis@gold.ac.uk>
date Fri, 13 Apr 2007 11:09:09 +0100
parents
children 1d757c33e00e
rev   line source
d@33 1 ;; Make midifiles from basic amuse objects methods here can be
d@33 2 ;; overridden for more specific types
d@33 3 ;;
d@33 4
d@33 5 ;; FIXME: Need to push some structures from geerdes to make this work.
d@33 6
d@33 7 (in-package #:amuse-utils)
d@33 8
d@33 9 (defgeneric play (music)
d@33 10 (:method (m) (play-midifile (make-midi m))))
d@33 11 (defmethod play ((music composition))
d@33 12 (play-midifile (make-midi music)))
d@33 13
d@33 14 (defun play-midifile (midifile)
d@33 15 ;; coremidi is easy as an alternative, but we'll probably want midi
d@33 16 ;; file export anyway, so it makes some sense to focus our efforts
d@33 17 ;; on this first. That said, is there a CoreAudio midi file player
d@33 18 ;; routine?
d@33 19 (midi:write-midi-file midifile "tmp.mid")
d@33 20 #+darwin
d@33 21 (when (sb-impl::find-executable-in-search-path "open")
d@33 22 (asdf:run-shell-command "open tmp.mid")
d@33 23 (return-from play-midifile T))
d@33 24 (when (sb-impl::find-executable-in-search-path "timidity")
d@33 25 (asdf:run-shell-command "timidity tmp.mid")
d@33 26 (return-from play-midifile T)))
d@33 27
d@33 28 (defgeneric make-midi (sequence))
d@33 29 (defmethod make-midi ((sequence sequence))
d@33 30 ;; Make a midifile object. Collects global midi messages (that
d@33 31 ;; require a sequence) and event-based messages (that don't).
d@33 32 ;; FIXME: Something about this strikes me as very stupid. Must
d@33 33 ;; revisit
d@33 34 ;; FIXME: Only making type 0. Is this a problem?
d@33 35 (let* ((events (event-sequence-messages sequence))
d@33 36 (globals (global-messages sequence))
d@33 37 (patches (patch-messages sequence)))
d@33 38 (make-midifile-from-messages (nconc events globals patches)
d@33 39 :type 0)))
d@33 40
d@33 41 (defun make-midifile-from-messages (events &key (type 0))
d@33 42 ;; FIXME: clearly broken if type 1
d@33 43 ;; First have to get rid of all fractional times and choose a
d@33 44 ;; timebase
d@33 45 (let* ((timebase (apply #'lcm (mapcar #'(lambda (x)
d@33 46 (denominator
d@33 47 (midi:message-time x)))
d@33 48 events))))
d@33 49 (when (< timebase 4)
d@33 50 (setf timebase (* 4 timebase)))
d@33 51 (loop for e in events
d@33 52 do (setf (midi:message-time e) (* timebase
d@33 53 (midi:message-time e))))
d@33 54 (make-instance 'midi:midifile
d@33 55 :format type
d@33 56 :division timebase
d@33 57 :tracks (list (sort-midi-messages-for-output events)))))
d@33 58
d@33 59 (defun sort-midi-messages-for-output (messages)
d@33 60 (sort messages #'(lambda (x y) (or (< (midi:message-time x)
d@33 61 (midi:message-time y))
d@33 62 (and (= (midi:message-time x)
d@33 63 (midi:message-time y))
d@33 64 (> (midi::message-status x)
d@33 65 (midi::message-status y)))))))
d@33 66
d@33 67 (defun event-sequence-messages (sequence)
d@33 68 (let ((midinotes))
d@33 69 (sequence:dosequence (event sequence midinotes)
d@33 70 (let ((messages (event-messages event)))
d@33 71 (dolist (message messages)
d@33 72 (push message midinotes))))))
d@33 73
d@33 74 (defun patch-messages (sequence)
d@33 75 (let ((patches (make-array 16 :initial-element nil))
d@33 76 (patch-list)
d@33 77 (channel 0)
d@33 78 (patch 0))
d@33 79 (sequence:dosequence (event sequence patch-list)
d@33 80 (setf channel (get-channel-for-midi event)
d@33 81 patch (get-patch-for-midi event))
d@33 82 (when (or (not (aref patches channel))
d@33 83 (not (= (aref patches channel)
d@33 84 patch)))
d@33 85 (push (make-instance 'midi:program-change-message
d@33 86 :program patch
d@33 87 :time (timepoint event)
d@33 88 :status (+ channel 192))
d@33 89 patch-list)
d@33 90 (setf (aref patches channel) patch)))))
d@33 91
d@33 92
d@33 93 (defgeneric global-messages (sequence)
d@33 94 (:method (s) (declare (ignore s)) nil))
d@33 95 (defmethod global-messages ((sequence composition))
d@33 96 ;; FIXME: missing plenty of other messages
d@33 97 ;; FIXME: messy
d@33 98 (let ((tempi (tempi sequence))
d@33 99 (temp)
d@33 100 (time-sigs (time-signatures sequence))
d@33 101 (events))
d@33 102 (dolist (tempo tempi)
d@33 103 (setf temp (tempo-message tempo))
d@33 104 (when temp
d@33 105 (push temp events)))
d@33 106 (dolist (time-sig time-sigs events)
d@33 107 (setf temp (time-sig-message time-sig))
d@33 108 (when temp
d@33 109 (push temp events)))))
d@33 110
d@33 111 (defgeneric tempo-message (tempo)
d@33 112 (:method (tp)
d@33 113 (progn
d@33 114 (let ((speed (make-instance 'midi:tempo-message
d@33 115 :time (timepoint tp)
d@33 116 :status 255)))
d@33 117 (setf (slot-value speed 'midi::tempo) (microseconds-per-crotchet tp))
d@33 118 speed))))
d@33 119
d@33 120 (defgeneric time-sig-message (time-sig)
d@33 121 (:method (ts) (declare (ignore ts)) nil))
d@33 122
d@33 123 (defgeneric event-messages (event)
d@33 124 (:method (e) (declare (ignore e)) nil))
d@33 125 (defmethod event-messages ((event pitched-event))
d@33 126 (list (make-instance 'midi:note-on-message
d@33 127 :status (+ (get-channel-for-midi event) 144)
d@33 128 :key (midi-pitch-number event)
d@33 129 :velocity (get-velocity-for-midi event)
d@33 130 :time (timepoint event))
d@33 131 (make-instance 'midi:note-off-message
d@33 132 :status (+ (get-channel-for-midi event) 128)
d@33 133 :key (midi-pitch-number event)
d@33 134 :velocity (get-velocity-for-midi event)
d@33 135 :time (timepoint (cut-off event)))))
d@33 136
d@33 137 (defmethod event-messages ((event percussive-event))
d@33 138 (list (make-instance 'midi:note-on-message
d@33 139 :status 153
d@33 140 :key (get-pitch-for-midi event)
d@33 141 :velocity (get-velocity-for-midi event)
d@33 142 :time (timepoint event))
d@33 143 (make-instance 'midi:note-off-message
d@33 144 :status 137
d@33 145 :key (get-pitch-for-midi event)
d@33 146 :velocity (get-velocity-for-midi event)
d@33 147 :time (timepoint (cut-off event)))))
d@33 148
d@33 149 (defgeneric get-pitch-for-midi (event))
d@33 150 (defgeneric get-velocity-for-midi (event)
d@33 151 (:method (e) (declare (ignore e)) 100))
d@33 152 (defgeneric get-patch-for-midi (event)
d@33 153 (:method (e) (declare (ignore e)) 0))
d@33 154 (defgeneric get-channel-for-midi (event)
d@33 155 (:method (e) (declare (ignore e)) 0))