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)))