Mercurial > hg > amuse
comparison tools/gsharp-output.lisp @ 164:27e29dd5978b
Add gsharp-output. *Warning: amuse-gsharp has moved*
darcs-hash:20071221114845-40ec0-c296d15b1bb242c36febcb33f4e6266680999818.gz
author | d.lewis <d.lewis@gold.ac.uk> |
---|---|
date | Fri, 21 Dec 2007 11:48:45 +0000 |
parents | |
children | db4acf840bf0 |
comparison
equal
deleted
inserted
replaced
163:83023a2668d2 | 164:27e29dd5978b |
---|---|
1 ;; This file is for methods creating and using gsharp score buffers | |
2 ;; for output. This would generally involve rendering to screen, file, | |
3 ;; printer or browser. | |
4 ;; | |
5 ;; The interface doesn't obviously align perfectly with the gsharp | |
6 ;; data structure, so some notes on the key elements are included | |
7 ;; here: | |
8 ;; | |
9 ;; amuse:events have no direct analogue, with their attributes divided | |
10 ;; between notes and clusters: | |
11 ;; | |
12 ;; * notes have a pitch with a number that is 1- the diatonic | |
13 ;; component of mips pitch and an accidental that is one of a list of | |
14 ;; keyword options, most relevant being :sharp :natural :flat | |
15 ;; :double-sharp and :double-flat | |
16 ;; | |
17 ;; * clusters are the rhythmic and positional aspect of one or more | |
18 ;; notes. Duration is stored in terms of notehead (:long :breve :whole | |
19 ;; :half :filled) and, for :filled, lbeams and rbeams for the number | |
20 ;; of beams in each direction, supplemented by a dots slot. To render | |
21 ;; this to a number for standard time within amuse, there is a method, | |
22 ;; gsharp:duration, for which a crotchet is 1/4 | |
23 ;; | |
24 ;; Parts and staffs are viewed notationally, so we have largely | |
25 ;; independant structures called layers (roughly = voices) and staves | |
26 ;; (as they appear on the page). These have names and, at the moment, | |
27 ;; are being identified by a string-getting method below. Within a | |
28 ;; layer, clusters are to be found in bar objects and listed in the | |
29 ;; bars slot. | |
30 ;; | |
31 ;; The musical entity into which these slot is a segment, and there | |
32 ;; can be multiple segments in a buffer, though what this means and | |
33 ;; how they relate is as yet a mystery to me. | |
34 ;; | |
35 ;;; IMPLEMENTATION | |
36 ;; | |
37 ;; We're creating a buffer. Lots of this code is adapted from | |
38 ;; gsharp/mxml/mxml.lisp, but wouldn't now be recognised | |
39 ;; | |
40 ;; * Clef is guessed at - we need amuse:get-applicable-clef (and | |
41 ;; amuse:clef) | |
42 | |
43 (in-package "AMUSE-TOOLS") | |
44 | |
45 (defstruct gsharp-duration | |
46 "Structure for specifying duration-related parameters for g-sharp" | |
47 notehead (beams 0) (dots 0) (tied-p nil)) | |
48 | |
49 ;;;;;;;;;;;;;;;;;;;;; | |
50 ;; Top-level methods | |
51 | |
52 (defun write-gsharp-eps (composition pathname) | |
53 ;; write a score eps from a composition. Most of this can be copied | |
54 ;; straight (this is copied already from CSR's code) | |
55 ;; Boilerplate stuff: | |
56 (let* ((frame (clim:make-application-frame 'gsharp:gsharp)) | |
57 (clim:*application-frame* frame) | |
58 (esa:*esa-instance* frame)) | |
59 (clim:adopt-frame (clim:find-frame-manager :server-path '(:null)) frame) | |
60 (clim:execute-frame-command frame '(gsharp::com-new-buffer)) | |
61 ;; Now generate the buffer | |
62 (fill-gsharp-buffer-with-constituent (car (esa:buffers frame)) composition) | |
63 ;; Refresh and process | |
64 (setf (gsharp::modified-p (car (esa:buffers frame))) t) | |
65 (gsharp::recompute-measures (car (esa:buffers frame))) | |
66 ;; Print | |
67 (clim:execute-frame-command | |
68 frame `(gsharp::com-print-buffer-to-file ,pathname)))) | |
69 | |
70 ;;;;;;;;;;;;;;;;;;;;;;;; | |
71 ;; | |
72 ;; Big `walking through data structures' type functions | |
73 | |
74 (defgeneric fill-gsharp-buffer-with-constituent (buffer constituent) | |
75 (:documentation "Takes an empty gsharp buffer and a constituent and | |
76 fills the buffer based on the contents of constituent. Buffer is | |
77 returned, but this is not necessary, since it is modified in | |
78 place.")) | |
79 (defmethod fill-gsharp-buffer-with-constituent (buffer (composition composition)) | |
80 ;; FIXME: Throughout this, I assume that | |
81 ;; get-applicable-time-signatures isn't '() | |
82 (let ((time-signatures (get-applicable-time-signatures composition composition)) | |
83 (layers)) | |
84 (multiple-value-bind (layer-events layer-scores) | |
85 ;; Get hash-tables of events by layer and counts of events | |
86 ;; below middle C for guessing clef. | |
87 (identify-gsharp-layers composition) | |
88 ;; For each layer make clef, and one staff per layer | |
89 ;; FIXME: this is cheating | |
90 (maphash #'(lambda (name events) | |
91 (let* ((clef (if (> (gethash name layer-scores) | |
92 (/ (length events) 2)) | |
93 (gsharp::make-clef :bass) | |
94 (gsharp::make-clef :treble))) | |
95 (staff (gsharp::make-fiveline-staff :name name | |
96 :clef clef))) | |
97 ;; Make the layers | |
98 (push (gsharp::make-layer (list staff) | |
99 :body (gsharp::make-slice :bars nil) | |
100 :name name) | |
101 layers) | |
102 ;; Add the notes and bars | |
103 (add-bars-and-events (car layers) (reverse events) time-signatures))) | |
104 layer-events) | |
105 ;; Attach layers to a segment and place segment into buffer | |
106 (let* ((segment (make-instance 'gsharp::segment :layers layers :buffer buffer))) | |
107 (setf (gsharp::segments buffer) (list segment) | |
108 (gsharp::staves buffer) (mapcar #'(lambda (x) (car (gsharp::staves x))) | |
109 layers)) | |
110 buffer)))) | |
111 | |
112 (defgeneric identify-gsharp-layers (constituent) | |
113 (:documentation "Takes a composition, returns two hash tables, one | |
114 of events grouped by layer name and the other of counts of notes | |
115 below middle C by layer (for choosing clef). N.B. On two counts this | |
116 is a dodgy way of tying clef, staff and layer. Separate later.")) | |
117 | |
118 (defmethod identify-gsharp-layers ((composition composition)) | |
119 (let ((layer-events (make-hash-table :test #'equal)) | |
120 (layer-scores (make-hash-table :test #'equal))) | |
121 (sequence:dosequence (event composition (values layer-events layer-scores)) | |
122 (when (pitchedp event) | |
123 (cond | |
124 ((gethash (gsharp-layer-string event) layer-events) | |
125 (push event (gethash (gsharp-layer-string event) layer-events)) | |
126 (when (< (midi-pitch-number event) 60) | |
127 (incf (gethash (gsharp-layer-string event) layer-scores)))) | |
128 (t (setf (gethash (gsharp-layer-string event) layer-events) | |
129 (list event) | |
130 (gethash (gsharp-layer-string event) layer-scores) | |
131 (if (< (midi-pitch-number event) 60) | |
132 1 0)))))))) | |
133 | |
134 (defun add-bars-and-events (layer events time-signatures) | |
135 "Given list of events to be attached to a layer, along with | |
136 applicable time signatures, clumsily waddle through them all and | |
137 slap an approximation to the events into place. Improve this." | |
138 (let* ((crotchet-beats (duration (crotchet (car events)))) ;; (or make into semibreves?) | |
139 (bar-starts (bar-starts time-signatures :crotchet crotchet-beats)) | |
140 (cluster) (bar (gsharp::make-melody-bar)) (cluster-time) (bar-no 0) | |
141 (body (gsharp::body layer))) | |
142 (gsharp::add-bar bar body bar-no) | |
143 (do* ((events events (cdr events)) | |
144 (event (car events) (car events))) | |
145 ((null events) layer) | |
146 (when (and (cdr bar-starts) | |
147 (>= (timepoint event) (second bar-starts))) | |
148 ;; new bar - and add 0 or more empty bars | |
149 (dotimes (i (- (or (position-if #'(lambda (x) (> x (timepoint event))) | |
150 bar-starts) | |
151 2) | |
152 2)) | |
153 ;; This only runs if one or more whole-bar rests is needed | |
154 ;; in a layer. | |
155 (incf bar-no) | |
156 (setf bar (gsharp::make-melody-bar)) | |
157 (gsharp::add-bar bar body bar-no) | |
158 (let* ((new-duration (quick-gs-duration (if (cdr bar-starts) | |
159 (- (second bar-starts) | |
160 (first bar-starts)) | |
161 4))) | |
162 (rest (gsharp::make-rest (car (gsharp::staves layer)) | |
163 :notehead (gsharp-duration-notehead new-duration) | |
164 :lbeams (gsharp-duration-beams new-duration) | |
165 :rbeams (gsharp-duration-beams new-duration) | |
166 :dots (gsharp-duration-dots new-duration)))) | |
167 (gsharp-mxml::add-element-at-duration rest bar 0)) | |
168 (setf bar-starts (cdr bar-starts))) | |
169 ;; Move on to new bar | |
170 (setf bar (gsharp::make-melody-bar) | |
171 bar-starts (cdr bar-starts)) | |
172 (incf bar-no) | |
173 (gsharp::add-bar bar body bar-no)) | |
174 (when (or (not cluster-time) | |
175 (not (time= cluster-time (onset event)))) | |
176 ;; Not part of a pre-existant chord (this will have more | |
177 ;; complicated logic when I add ties). Create a new cluster. | |
178 (let* ((beat-duration (/ (duration event) crotchet-beats)) | |
179 ;; This hideous thing gives a duration that doesn't | |
180 ;; overlap with the next note or barline. Unneccessary | |
181 ;; when I fix ties and start supplying bar info to | |
182 ;; duration function. | |
183 (new-duration (quick-gs-duration (min beat-duration | |
184 (if (or (not (cdr events)) | |
185 (time= (second events) | |
186 event)) | |
187 beat-duration | |
188 (/ (- (timepoint (second events)) | |
189 (timepoint event)) | |
190 crotchet-beats)) | |
191 (if (cdr bar-starts) | |
192 (- (second bar-starts) | |
193 (/ (timepoint event) | |
194 crotchet-beats)) | |
195 beat-duration))))) | |
196 (setf cluster (gsharp::make-cluster :notehead (gsharp-duration-notehead new-duration) | |
197 :lbeams (gsharp-duration-beams new-duration) | |
198 :rbeams (gsharp-duration-beams new-duration) | |
199 :dots (gsharp-duration-dots new-duration))) | |
200 ;; This function adds cluster at a specific point in the | |
201 ;; bar. It does a lot of other things that are probably a) | |
202 ;; not necessary or b) should be within the duration logic | |
203 ;; above. Would be good not to rely on it (which is not to | |
204 ;; say that it isn't reliable) | |
205 (gsharp-mxml::add-element-at-duration cluster bar (/ (- (/ (timepoint event) | |
206 crotchet-beats) | |
207 (car bar-starts)) | |
208 4)) | |
209 (setf cluster-time (onset event)))) | |
210 (let ((pitch (pitch-for-gsharp event))) | |
211 (gsharp::add-note cluster (make-instance 'gsharp::note | |
212 :pitch (first pitch) | |
213 :accidentals (second pitch) | |
214 :staff (car (gsharp::staves layer)))))))) | |
215 | |
216 ;;;;;;;;;;;;;;;;;;;;;;; | |
217 ;; | |
218 ;; Information conversion functions | |
219 | |
220 ;; Pitch | |
221 | |
222 (defgeneric pitch-for-gsharp (pitch) | |
223 (:documentation "Given a pitch object, return a list of gsharp's | |
224 pitch number and accidental keyword")) | |
225 (defmethod pitch-for-gsharp ((pitch diatonic-pitch)) | |
226 ;; Easy for diatonic pitch, although behaviour for extreme or | |
227 ;; fractional alterations is unclear. | |
228 (list (1+ (diatonic-pitch-mp pitch)) | |
229 (case (diatonic-pitch-accidental pitch) | |
230 (1 :sharp) | |
231 (-1 :flat) | |
232 (2 :double-sharp) | |
233 (-2 :double-flat) | |
234 (0 :natural) | |
235 (otherwise (error "gsharp can't handle this pitch"))))) | |
236 (defmethod pitch-for-gsharp ((pitch chromatic-pitch)) | |
237 ;; Just go for line-of-fifths proximity spelling. Could always try | |
238 ;; to spell it, but... | |
239 (let* ((octave (octave pitch)) | |
240 (pitch-class (pitch-class pitch)) | |
241 (diatonic-pitch-number (aref #(0 0 1 2 2 3 3 4 4 5 6 6) pitch-class))) | |
242 (list (+ (* 7 octave) diatonic-pitch-number) | |
243 (aref #(:natural :sharp ;; C C# | |
244 :natural ;; D | |
245 :flat :natural ;; Eb E | |
246 :natural :sharp ;; F F# | |
247 :natural :sharp ;; G G# | |
248 :natural ;; A | |
249 :flat :natural) ;; Bb B | |
250 pitch-class)))) | |
251 | |
252 ;; Time | |
253 | |
254 (defun quick-gs-duration (crotchets) | |
255 ;; gsharp-durations-from-beats returns a list of durations. Take the | |
256 ;; first (= largest). | |
257 (car (gsharp-durations-from-beats crotchets))) | |
258 | |
259 (defun gsharp-durations-from-beats (beats &optional (durations nil)) | |
260 ;; Takes a count of crotchets and returns a list of | |
261 ;; <gsharp-duration>s that most simply defines the attached. This | |
262 ;; is a recursive function that finds the longest simple duration | |
263 ;; that fits into beats and, if that leaves a remainder, runs again | |
264 ;; on the remainder (until hemi-demi-semi-quavers are reached). It | |
265 ;; avoids double dots and is ignorant of time-signature. It will be | |
266 ;; replaced. Soon. | |
267 ;;; FIXME: Handles quantisation fairly | |
268 ;; stupidly. Could be made slightly smarter with simple rounding | |
269 ;; (erring on the side of longer durations?) | |
270 (assert (>= beats 0)) | |
271 (push (make-gsharp-duration) durations) | |
272 ;; First find the longest simple duration that fits in beats | |
273 ;; First with notes > 1 crotchet | |
274 (loop for option in '((16 :long) (8 :breve) (4 :whole) (2 :half) (1 :filled)) | |
275 do (cond | |
276 ((= beats (* (car option) 3/2)) | |
277 (setf (gsharp-duration-notehead (car durations)) | |
278 (cadr option) | |
279 (gsharp-duration-dots (car durations)) | |
280 1) | |
281 (return-from gsharp-durations-from-beats (reverse durations))) | |
282 ((> beats (car option)) | |
283 (setf (gsharp-duration-notehead (car durations)) | |
284 (cadr option)) | |
285 (return-from gsharp-durations-from-beats | |
286 (gsharp-durations-from-beats (- beats (car option)) durations))) | |
287 ((= beats (car option)) | |
288 (setf (gsharp-duration-notehead (car durations)) | |
289 (cadr option)) | |
290 (return-from gsharp-durations-from-beats (reverse durations))))) | |
291 (setf (gsharp-duration-notehead (car durations)) | |
292 :filled) | |
293 ;; then with short notes (beams rather than noteheads) | |
294 (do ((i 1 (1+ i))) | |
295 ((= i 4) ;; means either tuplet, very short note or unquantised data | |
296 (reverse durations)) | |
297 (cond | |
298 ((= beats (* (/ 1 (expt 2 i)) 3/2)) | |
299 (setf (gsharp-duration-beams (car durations)) | |
300 i | |
301 (gsharp-duration-dots (car durations)) | |
302 1) | |
303 (return-from gsharp-durations-from-beats (reverse durations))) | |
304 ((> beats (/ 1 (expt 2 i))) | |
305 (setf (gsharp-duration-beams (car durations)) | |
306 i) | |
307 (return-from gsharp-durations-from-beats | |
308 (gsharp-durations-from-beats (- beats (/ 1 (expt 2 i))) durations))) | |
309 ((= beats (/ 1 (expt 2 i))) | |
310 (setf (gsharp-duration-beams (car durations)) | |
311 i) | |
312 (return-from gsharp-durations-from-beats (reverse durations)))))) | |
313 | |
314 ;;;;;;;;;;;;;;;;;;;;; | |
315 ;; | |
316 ;; Other utility functions | |
317 | |
318 (defgeneric gsharp-layer-string (event) | |
319 (:method (e) (name-from-channel-and-patch e)) | |
320 (:documentation "Return a string that uniquely identifies the layer | |
321 to which event belongs")) | |
322 | |
323 (defun name-from-channel-and-patch (event) | |
324 "Generate layer-identifying string from the patch and channel that | |
325 would be used for midi export. For MIDI, this is guaranteed to | |
326 separate or over-separate. Tracks would possibly be better, but ?don't | |
327 exist in MIDI type 0?" | |
328 (format nil "~D/~D" | |
329 (get-patch-for-midi event) | |
330 (get-channel-for-midi event))) | |
331 | |
332 (defun bar-starts (time-signature-list &key (crotchet 1)) | |
333 (loop for time-signature in time-signature-list | |
334 nconc (loop for i from (timepoint (onset time-signature)) | |
335 to (1- (timepoint (cut-off time-signature))) | |
336 by (* (crotchets-in-a-bar time-signature) crotchet) | |
337 collect i))) |