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