d@164
|
1 ;; This file is for methods creating and using gsharp score buffers
|
d@164
|
2 ;; for output. This would generally involve rendering to screen, file,
|
d@164
|
3 ;; printer or browser.
|
d@164
|
4 ;;
|
d@164
|
5 ;; The interface doesn't obviously align perfectly with the gsharp
|
d@164
|
6 ;; data structure, so some notes on the key elements are included
|
d@164
|
7 ;; here:
|
d@164
|
8 ;;
|
d@164
|
9 ;; amuse:events have no direct analogue, with their attributes divided
|
d@164
|
10 ;; between notes and clusters:
|
d@164
|
11 ;;
|
d@164
|
12 ;; * notes have a pitch with a number that is 1- the diatonic
|
d@164
|
13 ;; component of mips pitch and an accidental that is one of a list of
|
d@164
|
14 ;; keyword options, most relevant being :sharp :natural :flat
|
d@164
|
15 ;; :double-sharp and :double-flat
|
d@164
|
16 ;;
|
d@164
|
17 ;; * clusters are the rhythmic and positional aspect of one or more
|
d@164
|
18 ;; notes. Duration is stored in terms of notehead (:long :breve :whole
|
d@164
|
19 ;; :half :filled) and, for :filled, lbeams and rbeams for the number
|
d@164
|
20 ;; of beams in each direction, supplemented by a dots slot. To render
|
d@164
|
21 ;; this to a number for standard time within amuse, there is a method,
|
d@164
|
22 ;; gsharp:duration, for which a crotchet is 1/4
|
d@164
|
23 ;;
|
d@164
|
24 ;; Parts and staffs are viewed notationally, so we have largely
|
d@164
|
25 ;; independant structures called layers (roughly = voices) and staves
|
d@164
|
26 ;; (as they appear on the page). These have names and, at the moment,
|
d@164
|
27 ;; are being identified by a string-getting method below. Within a
|
d@164
|
28 ;; layer, clusters are to be found in bar objects and listed in the
|
d@164
|
29 ;; bars slot.
|
d@164
|
30 ;;
|
d@164
|
31 ;; The musical entity into which these slot is a segment, and there
|
d@164
|
32 ;; can be multiple segments in a buffer, though what this means and
|
d@164
|
33 ;; how they relate is as yet a mystery to me.
|
d@164
|
34 ;;
|
d@164
|
35 ;;; IMPLEMENTATION
|
d@164
|
36 ;;
|
d@164
|
37 ;; We're creating a buffer. Lots of this code is adapted from
|
d@164
|
38 ;; gsharp/mxml/mxml.lisp, but wouldn't now be recognised
|
d@164
|
39 ;;
|
d@164
|
40 ;; * Clef is guessed at - we need amuse:get-applicable-clef (and
|
d@164
|
41 ;; amuse:clef)
|
d@164
|
42
|
d@164
|
43 (in-package "AMUSE-TOOLS")
|
d@164
|
44
|
d@166
|
45 (defparameter *rounding-factor* 1/4)
|
d@166
|
46
|
d@164
|
47 (defstruct gsharp-duration
|
d@164
|
48 "Structure for specifying duration-related parameters for g-sharp"
|
d@164
|
49 notehead (beams 0) (dots 0) (tied-p nil))
|
d@164
|
50
|
d@164
|
51 ;;;;;;;;;;;;;;;;;;;;;
|
d@164
|
52 ;; Top-level methods
|
d@164
|
53
|
d@164
|
54 (defun write-gsharp-eps (composition pathname)
|
d@164
|
55 ;; write a score eps from a composition. Most of this can be copied
|
d@164
|
56 ;; straight (this is copied already from CSR's code)
|
d@164
|
57 ;; Boilerplate stuff:
|
d@164
|
58 (let* ((frame (clim:make-application-frame 'gsharp:gsharp))
|
d@164
|
59 (clim:*application-frame* frame)
|
d@164
|
60 (esa:*esa-instance* frame))
|
d@164
|
61 (clim:adopt-frame (clim:find-frame-manager :server-path '(:null)) frame)
|
d@164
|
62 (clim:execute-frame-command frame '(gsharp::com-new-buffer))
|
d@164
|
63 ;; Now generate the buffer
|
d@164
|
64 (fill-gsharp-buffer-with-constituent (car (esa:buffers frame)) composition)
|
d@164
|
65 ;; Refresh and process
|
d@164
|
66 (setf (gsharp::modified-p (car (esa:buffers frame))) t)
|
d@164
|
67 (gsharp::recompute-measures (car (esa:buffers frame)))
|
d@164
|
68 ;; Print
|
d@164
|
69 (clim:execute-frame-command
|
d@164
|
70 frame `(gsharp::com-print-buffer-to-file ,pathname))))
|
d@164
|
71
|
d@164
|
72 ;;;;;;;;;;;;;;;;;;;;;;;;
|
d@164
|
73 ;;
|
d@164
|
74 ;; Big `walking through data structures' type functions
|
d@164
|
75
|
d@164
|
76 (defgeneric fill-gsharp-buffer-with-constituent (buffer constituent)
|
d@164
|
77 (:documentation "Takes an empty gsharp buffer and a constituent and
|
d@164
|
78 fills the buffer based on the contents of constituent. Buffer is
|
d@164
|
79 returned, but this is not necessary, since it is modified in
|
d@164
|
80 place."))
|
d@164
|
81 (defmethod fill-gsharp-buffer-with-constituent (buffer (composition composition))
|
d@164
|
82 ;; FIXME: Throughout this, I assume that
|
d@164
|
83 ;; get-applicable-time-signatures isn't '()
|
d@164
|
84 (let ((time-signatures (get-applicable-time-signatures composition composition))
|
d@166
|
85 (key-signatures (get-applicable-key-signatures composition composition))
|
d@164
|
86 (layers))
|
d@164
|
87 (multiple-value-bind (layer-events layer-scores)
|
d@164
|
88 ;; Get hash-tables of events by layer and counts of events
|
d@164
|
89 ;; below middle C for guessing clef.
|
d@164
|
90 (identify-gsharp-layers composition)
|
d@164
|
91 ;; For each layer make clef, and one staff per layer
|
d@164
|
92 ;; FIXME: this is cheating
|
d@164
|
93 (maphash #'(lambda (name events)
|
d@164
|
94 (let* ((clef (if (> (gethash name layer-scores)
|
d@164
|
95 (/ (length events) 2))
|
d@164
|
96 (gsharp::make-clef :bass)
|
d@164
|
97 (gsharp::make-clef :treble)))
|
d@164
|
98 (staff (gsharp::make-fiveline-staff :name name
|
d@164
|
99 :clef clef)))
|
d@164
|
100 ;; Make the layers
|
d@164
|
101 (push (gsharp::make-layer (list staff)
|
d@164
|
102 :body (gsharp::make-slice :bars nil)
|
d@164
|
103 :name name)
|
d@164
|
104 layers)
|
d@164
|
105 ;; Add the notes and bars
|
d@166
|
106 (add-bars-and-events-to-layer (car layers) (reverse events)
|
d@168
|
107 time-signatures composition
|
d@166
|
108 :key-signatures key-signatures)))
|
d@164
|
109 layer-events)
|
d@164
|
110 ;; Attach layers to a segment and place segment into buffer
|
d@164
|
111 (let* ((segment (make-instance 'gsharp::segment :layers layers :buffer buffer)))
|
d@164
|
112 (setf (gsharp::segments buffer) (list segment)
|
d@164
|
113 (gsharp::staves buffer) (mapcar #'(lambda (x) (car (gsharp::staves x)))
|
d@164
|
114 layers))
|
d@164
|
115 buffer))))
|
d@164
|
116
|
d@164
|
117 (defgeneric identify-gsharp-layers (constituent)
|
d@164
|
118 (:documentation "Takes a composition, returns two hash tables, one
|
d@164
|
119 of events grouped by layer name and the other of counts of notes
|
d@164
|
120 below middle C by layer (for choosing clef). N.B. On two counts this
|
d@164
|
121 is a dodgy way of tying clef, staff and layer. Separate later."))
|
d@164
|
122
|
d@164
|
123 (defmethod identify-gsharp-layers ((composition composition))
|
d@164
|
124 (let ((layer-events (make-hash-table :test #'equal))
|
d@164
|
125 (layer-scores (make-hash-table :test #'equal)))
|
d@164
|
126 (sequence:dosequence (event composition (values layer-events layer-scores))
|
d@164
|
127 (when (pitchedp event)
|
d@164
|
128 (cond
|
d@164
|
129 ((gethash (gsharp-layer-string event) layer-events)
|
d@164
|
130 (push event (gethash (gsharp-layer-string event) layer-events))
|
d@164
|
131 (when (< (midi-pitch-number event) 60)
|
d@164
|
132 (incf (gethash (gsharp-layer-string event) layer-scores))))
|
d@164
|
133 (t (setf (gethash (gsharp-layer-string event) layer-events)
|
d@164
|
134 (list event)
|
d@164
|
135 (gethash (gsharp-layer-string event) layer-scores)
|
d@164
|
136 (if (< (midi-pitch-number event) 60)
|
d@164
|
137 1 0))))))))
|
d@164
|
138
|
d@168
|
139 (defun add-bars-and-events-to-layer (layer events time-signatures composition
|
d@168
|
140 &key (key-signatures nil))
|
d@164
|
141 "Given list of events to be attached to a layer, along with
|
d@164
|
142 applicable time signatures, clumsily waddle through them all and
|
d@164
|
143 slap an approximation to the events into place. Improve this."
|
d@164
|
144 (let* ((crotchet-beats (duration (crotchet (car events)))) ;; (or make into semibreves?)
|
d@166
|
145 (beat-starts (beat-starts time-signatures
|
d@166
|
146 :crotchet crotchet-beats))
|
d@166
|
147 (bar-starts (mapcar #'car beat-starts))
|
d@166
|
148 (ons)
|
d@166
|
149 (clusters) (bar) (bar-no 0)
|
d@164
|
150 (body (gsharp::body layer)))
|
d@166
|
151 ;; this is a cheat to guess timing rounding (quantisation) based
|
d@166
|
152 ;; on onset times - only affects midi-like data where onsets are
|
d@166
|
153 ;; already rounded, but durations are not (as in TC's fantasia
|
d@166
|
154 ;; midi files....)
|
d@166
|
155 (setf *rounding-factor* (max (guess-rounding-factor events)
|
d@166
|
156 1/8))
|
d@166
|
157 ;; First create a list of change-points for when events are
|
d@166
|
158 ;; sounding, of the format (time event event event) (time event))
|
d@166
|
159 (dolist (event events)
|
d@166
|
160 (setf ons (add-on-off-pair event ons)))
|
d@166
|
161 ;; These durations may span bars, which is an absolute ban for
|
d@166
|
162 ;; most music (Mensurstrich aside), so insert bar starts if not
|
d@166
|
163 ;; present. Note that, since the events themselves are recorded in
|
d@166
|
164 ;; the list, the existence of ties shuold be detected.
|
d@166
|
165 (when bar-starts
|
d@166
|
166 (setf ons (add-bar-starts-if-not-present bar-starts ons)))
|
d@166
|
167 ;; Finally, one problem here is that, in midi, there is often a
|
d@166
|
168 ;; gap or overlap between consecutive notes or chords. Since
|
d@166
|
169 ;; rounding happens, but there is no check for bar length here or
|
d@166
|
170 ;; within g-sharp, this should verify that everything makes
|
d@166
|
171 ;; sense. At the moment, it just removes short rests...
|
d@166
|
172 (setf ons (check-ons ons))
|
d@166
|
173 ;; Now create the bars and the gsharp clusters
|
d@168
|
174 (when key-signatures
|
d@168
|
175 (setf (gsharp::keysig (car (gsharp::staves layer)))
|
d@168
|
176 (make-gsharp-key-signature (car key-signatures) layer)))
|
d@166
|
177 (do ((old-ons nil ons)
|
d@166
|
178 (ons ons (cdr ons)))
|
d@166
|
179 ((null (cdr ons)))
|
d@166
|
180 (when (member (caar ons) bar-starts)
|
d@166
|
181 (setf bar (gsharp::make-melody-bar))
|
d@166
|
182 (gsharp::add-bar bar body bar-no)
|
d@166
|
183 (incf bar-no))
|
d@168
|
184 (when (and key-signatures
|
d@168
|
185 (<= (timepoint (car key-signatures))
|
d@168
|
186 (caar ons)))
|
d@168
|
187 (gsharp-mxml::add-element-at-duration (make-gsharp-key-signature (car key-signatures) layer)
|
d@168
|
188 bar
|
d@168
|
189 (/ (- (timepoint (car key-signatures))
|
d@168
|
190 (caar beat-starts))
|
d@168
|
191 4))
|
d@168
|
192 (setf key-signatures (cdr key-signatures)))
|
d@166
|
193 ;; A quick check for notes which span beats and don't start at
|
d@166
|
194 ;; the beginning of their beats. IMO, this makes them more
|
d@166
|
195 ;; likely to require a tie.
|
d@166
|
196 (when (and (not (member (caar ons)
|
d@166
|
197 (car beat-starts)))
|
d@166
|
198 (find-if #'(lambda (x) (> x (caar ons)))
|
d@166
|
199 (car beat-starts))
|
d@166
|
200 (< (find-if #'(lambda (x) (> x (caar ons)))
|
d@166
|
201 (car beat-starts))
|
d@166
|
202 (car (second ons))))
|
d@166
|
203 (setf (cdr ons)
|
d@166
|
204 (cons (cons (find-if #'(lambda (x) (> x (caar ons)))
|
d@166
|
205 (car beat-starts))
|
d@166
|
206 (cdar ons))
|
d@166
|
207 (cdr ons))))
|
d@166
|
208 ;; Making clusters just from duration removes the ability to
|
d@166
|
209 ;; divide notes into easy-to-read tied components based on the
|
d@166
|
210 ;; time signature (for example, a note of a tactus beat + a
|
d@166
|
211 ;; quaver in 6/8 will be rendered as a minim this way) - that's
|
d@166
|
212 ;; why I've taken as much of the metrical logic out and put it
|
d@166
|
213 ;; above if there are other straightforward rules, they should,
|
d@166
|
214 ;; I think go there.
|
d@166
|
215 (if (cdr (car ons))
|
d@166
|
216 (setf clusters (make-gsharp-clusters-with-duration (- (car (second ons))
|
d@166
|
217 (car (car ons)))))
|
d@166
|
218 (setf clusters (make-gsharp-rests-with-duration (- (car (second ons))
|
d@166
|
219 (car (car ons)))
|
d@166
|
220 layer)))
|
d@166
|
221 (let ((now (caar ons)) (first-p t))
|
d@166
|
222 (do ((clusters clusters (cdr clusters)))
|
d@166
|
223 ((null clusters))
|
d@166
|
224 (when (member now (car beat-starts))
|
d@166
|
225 (setf (gsharp::lbeams (car clusters)) 0))
|
d@164
|
226 ;; This function adds cluster at a specific point in the
|
d@164
|
227 ;; bar. It does a lot of other things that are probably a)
|
d@164
|
228 ;; not necessary or b) should be within the duration logic
|
d@164
|
229 ;; above. Would be good not to rely on it (which is not to
|
d@164
|
230 ;; say that it isn't reliable)
|
d@166
|
231 (gsharp-mxml::add-element-at-duration (car clusters)
|
d@166
|
232 bar
|
d@166
|
233 (/ (- now (car bar-starts))
|
d@166
|
234 4))
|
d@166
|
235 (dolist (note (cdr (car ons)))
|
d@168
|
236 (with-simple-restart (ignore "Ignore note")
|
d@168
|
237 (when note
|
d@168
|
238 (let ((pitch (pitch-for-gsharp note composition)))
|
d@168
|
239 (gsharp::add-note (car clusters)
|
d@168
|
240 (make-instance 'gsharp::note
|
d@168
|
241 :pitch (first pitch)
|
d@168
|
242 :accidentals (second pitch)
|
d@168
|
243 :staff (car (gsharp::staves layer))
|
d@168
|
244 :tie-right (if (cdr clusters)
|
d@168
|
245 t
|
d@168
|
246 (member note (second ons)))
|
d@168
|
247 :tie-left (if first-p
|
d@168
|
248 (member note (first old-ons))
|
d@168
|
249 t)))))))
|
d@166
|
250 (incf now (* (gsharp::duration (car clusters)) 4))
|
d@166
|
251 (setf first-p nil)))
|
d@166
|
252 (when (and (cdr bar-starts)
|
d@166
|
253 (= (car (second ons))
|
d@166
|
254 (second bar-starts)))
|
d@166
|
255 (setf bar-starts (cdr bar-starts)
|
d@168
|
256 beat-starts (cdr beat-starts))))))
|
d@168
|
257
|
d@168
|
258
|
d@168
|
259 (defgeneric make-gsharp-key-signature (key-signature layer))
|
d@168
|
260 (defmethod make-gsharp-key-signature ((key-signature standard-key-signature) layer)
|
d@168
|
261 (let ((alterations (make-array 7))
|
d@168
|
262 (order-of-sharps #(3 0 4 1 5 2 6))
|
d@168
|
263 (order-of-flats #(6 2 5 1 4 0 3)))
|
d@168
|
264 (if (< (key-signature-sharps key-signature) 0)
|
d@168
|
265 (dotimes (index (abs (key-signature-sharps key-signature)))
|
d@168
|
266 (setf (elt alterations (elt order-of-flats index)) :flat))
|
d@168
|
267 (dotimes (index (key-signature-sharps key-signature))
|
d@168
|
268 (setf (elt alterations (elt order-of-sharps index)) :sharp)))
|
d@168
|
269 (gsharp-buffer::make-key-signature (car (gsharp::staves layer))
|
d@168
|
270 :alterations alterations)))
|
d@164
|
271
|
d@164
|
272 ;;;;;;;;;;;;;;;;;;;;;;;
|
d@164
|
273 ;;
|
d@164
|
274 ;; Information conversion functions
|
d@164
|
275
|
d@164
|
276 ;; Pitch
|
d@164
|
277
|
d@168
|
278 (defgeneric pitch-for-gsharp (pitch composition)
|
d@164
|
279 (:documentation "Given a pitch object, return a list of gsharp's
|
d@164
|
280 pitch number and accidental keyword"))
|
d@168
|
281 (defmethod pitch-for-gsharp ((pitch diatonic-pitch) composition)
|
d@168
|
282 (declare (ignore composition))
|
d@164
|
283 ;; Easy for diatonic pitch, although behaviour for extreme or
|
d@164
|
284 ;; fractional alterations is unclear.
|
d@164
|
285 (list (1+ (diatonic-pitch-mp pitch))
|
d@164
|
286 (case (diatonic-pitch-accidental pitch)
|
d@164
|
287 (1 :sharp)
|
d@164
|
288 (-1 :flat)
|
d@164
|
289 (2 :double-sharp)
|
d@164
|
290 (-2 :double-flat)
|
d@164
|
291 (0 :natural)
|
d@164
|
292 (otherwise (error "gsharp can't handle this pitch")))))
|
d@168
|
293 (defmethod pitch-for-gsharp ((pitch chromatic-pitch) composition)
|
d@168
|
294 ;; Just go for line-of-fifths proximity spelling, but based on
|
d@168
|
295 ;; keysig if present. Could always try to spell it with ps13, but..
|
d@164
|
296 (let* ((octave (octave pitch))
|
d@164
|
297 (pitch-class (pitch-class pitch))
|
d@164
|
298 (diatonic-pitch-number (aref #(0 0 1 2 2 3 3 4 4 5 6 6) pitch-class)))
|
d@164
|
299 (list (+ (* 7 octave) diatonic-pitch-number)
|
d@164
|
300 (aref #(:natural :sharp ;; C C#
|
d@164
|
301 :natural ;; D
|
d@164
|
302 :flat :natural ;; Eb E
|
d@164
|
303 :natural :sharp ;; F F#
|
d@164
|
304 :natural :sharp ;; G G#
|
d@164
|
305 :natural ;; A
|
d@164
|
306 :flat :natural) ;; Bb B
|
d@164
|
307 pitch-class))))
|
d@168
|
308 (defmethod pitch-for-gsharp ((event standard-chromatic-pitched-event) composition)
|
d@168
|
309 ;; Should probably go for line-of-fifths proximity spelling,
|
d@168
|
310 ;; if keysig present, but ps13ing for now.
|
d@168
|
311 (let* ((octave (octave event))
|
d@168
|
312 (event-pos (position event composition))
|
d@168
|
313 (note-sequence (get-spelling-list composition))
|
d@168
|
314 (spelling (elt note-sequence event-pos))
|
d@168
|
315 (note-name (cdr (assoc (aref (second spelling) 0)
|
d@168
|
316 '(("C" . 0) ("D" . 1) ("E" . 2) ("F" . 3)
|
d@168
|
317 ("G" . 4) ("A" . 5) ("B" . 6))
|
d@168
|
318 :test #'string=)))
|
d@168
|
319 (accidental (cdr (assoc (aref (second spelling) 1)
|
d@168
|
320 '(("ss" . :double-sharp) ("s" . :sharp) ("n" . :natural)
|
d@168
|
321 ("f" . :flat) ("ff" . :double-flat))
|
d@168
|
322 :test #'string=))))
|
d@168
|
323 (list (+ (* 7 octave) note-name) accidental)))
|
d@168
|
324
|
d@168
|
325
|
d@168
|
326 (defparameter *spelling-cache* (make-hash-table))
|
d@168
|
327 (defparameter *ocp-cache* (make-hash-table))
|
d@168
|
328
|
d@168
|
329 (defun get-spelling-list (composition)
|
d@168
|
330 (unless (gethash composition *spelling-cache*)
|
d@168
|
331 (setf (gethash composition *spelling-cache*)
|
d@168
|
332 (ps13:ps13-new-imp (map 'list #'cdr (get-ocp-list composition))
|
d@168
|
333 10 42 nil nil nil)))
|
d@168
|
334 (gethash composition *spelling-cache*))
|
d@168
|
335
|
d@168
|
336 (defun get-ocp-list (composition)
|
d@168
|
337 (unless (gethash composition *ocp-cache*)
|
d@168
|
338 (setf (gethash composition *ocp-cache*) (ocp-list composition)))
|
d@168
|
339 (gethash composition *ocp-cache*))
|
d@168
|
340
|
d@168
|
341 (defun ocp-list (composition)
|
d@168
|
342 (flet ((sorter (x y)
|
d@168
|
343 (or (amuse:time< x y)
|
d@168
|
344 (and (amuse:time= x y)
|
d@168
|
345 (amuse:pitch< x y)))))
|
d@168
|
346 (loop for e being each element of composition
|
d@168
|
347 if (typep e 'amuse:pitched-event)
|
d@168
|
348 collect (cons e (make-array 2 :initial-contents
|
d@168
|
349 (list (slot-value e 'amuse::time)
|
d@168
|
350 (- (slot-value e 'amuse::number) 21)))) into result
|
d@168
|
351 finally (return (sort result #'sorter :key #'car)))))
|
d@168
|
352
|
d@168
|
353
|
d@168
|
354
|
d@164
|
355
|
d@164
|
356 ;; Time
|
d@164
|
357
|
d@166
|
358 (defun make-gsharp-clusters-with-duration (duration)
|
d@166
|
359 "Returns a list of cluster(s) whose total duration is equal to
|
d@166
|
360 duration (which is given in crotchets)"
|
d@166
|
361 (let ((new-durations (gsharp-durations-from-beats duration)))
|
d@166
|
362 (loop for new-duration in new-durations
|
d@166
|
363 collect (gsharp::make-cluster :notehead (gsharp-duration-notehead new-duration)
|
d@166
|
364 :lbeams (gsharp-duration-beams new-duration)
|
d@166
|
365 :rbeams (gsharp-duration-beams new-duration)
|
d@166
|
366 :dots (gsharp-duration-dots new-duration)))))
|
d@166
|
367
|
d@166
|
368 (defun make-gsharp-rests-with-duration (duration layer)
|
d@166
|
369 "Returns a list of rest(s) whose total duration is equal to
|
d@166
|
370 duration (which is given in crotchets)"
|
d@166
|
371 (let ((new-durations (gsharp-durations-from-beats duration)))
|
d@166
|
372 (loop for new-duration in new-durations
|
d@166
|
373 collect(gsharp::make-rest (car (gsharp::staves layer))
|
d@166
|
374 :notehead (gsharp-duration-notehead new-duration)
|
d@166
|
375 :lbeams (gsharp-duration-beams new-duration)
|
d@166
|
376 :rbeams (gsharp-duration-beams new-duration)
|
d@166
|
377 :dots (gsharp-duration-dots new-duration)))))
|
d@164
|
378
|
d@164
|
379 (defun gsharp-durations-from-beats (beats &optional (durations nil))
|
d@164
|
380 ;; Takes a count of crotchets and returns a list of
|
d@164
|
381 ;; <gsharp-duration>s that most simply defines the attached. This
|
d@164
|
382 ;; is a recursive function that finds the longest simple duration
|
d@164
|
383 ;; that fits into beats and, if that leaves a remainder, runs again
|
d@164
|
384 ;; on the remainder (until hemi-demi-semi-quavers are reached). It
|
d@164
|
385 ;; avoids double dots and is ignorant of time-signature. It will be
|
d@164
|
386 ;; replaced. Soon.
|
d@164
|
387 ;;; FIXME: Handles quantisation fairly
|
d@164
|
388 ;; stupidly. Could be made slightly smarter with simple rounding
|
d@164
|
389 ;; (erring on the side of longer durations?)
|
d@164
|
390 (assert (>= beats 0))
|
d@164
|
391 (push (make-gsharp-duration) durations)
|
d@164
|
392 ;; First find the longest simple duration that fits in beats
|
d@164
|
393 ;; First with notes > 1 crotchet
|
d@164
|
394 (loop for option in '((16 :long) (8 :breve) (4 :whole) (2 :half) (1 :filled))
|
d@164
|
395 do (cond
|
d@166
|
396 ((= beats (* (car option) 7/4))
|
d@166
|
397 (setf (gsharp-duration-notehead (car durations))
|
d@166
|
398 (cadr option)
|
d@166
|
399 (gsharp-duration-dots (car durations))
|
d@166
|
400 2)
|
d@166
|
401 (return-from gsharp-durations-from-beats (reverse durations)))
|
d@164
|
402 ((= beats (* (car option) 3/2))
|
d@164
|
403 (setf (gsharp-duration-notehead (car durations))
|
d@164
|
404 (cadr option)
|
d@164
|
405 (gsharp-duration-dots (car durations))
|
d@164
|
406 1)
|
d@164
|
407 (return-from gsharp-durations-from-beats (reverse durations)))
|
d@164
|
408 ((> beats (car option))
|
d@164
|
409 (setf (gsharp-duration-notehead (car durations))
|
d@164
|
410 (cadr option))
|
d@164
|
411 (return-from gsharp-durations-from-beats
|
d@164
|
412 (gsharp-durations-from-beats (- beats (car option)) durations)))
|
d@164
|
413 ((= beats (car option))
|
d@164
|
414 (setf (gsharp-duration-notehead (car durations))
|
d@164
|
415 (cadr option))
|
d@164
|
416 (return-from gsharp-durations-from-beats (reverse durations)))))
|
d@164
|
417 (setf (gsharp-duration-notehead (car durations))
|
d@164
|
418 :filled)
|
d@164
|
419 ;; then with short notes (beams rather than noteheads)
|
d@164
|
420 (do ((i 1 (1+ i)))
|
d@164
|
421 ((= i 4) ;; means either tuplet, very short note or unquantised data
|
d@164
|
422 (reverse durations))
|
d@164
|
423 (cond
|
d@164
|
424 ((= beats (* (/ 1 (expt 2 i)) 3/2))
|
d@164
|
425 (setf (gsharp-duration-beams (car durations))
|
d@164
|
426 i
|
d@164
|
427 (gsharp-duration-dots (car durations))
|
d@164
|
428 1)
|
d@164
|
429 (return-from gsharp-durations-from-beats (reverse durations)))
|
d@164
|
430 ((> beats (/ 1 (expt 2 i)))
|
d@164
|
431 (setf (gsharp-duration-beams (car durations))
|
d@164
|
432 i)
|
d@164
|
433 (return-from gsharp-durations-from-beats
|
d@164
|
434 (gsharp-durations-from-beats (- beats (/ 1 (expt 2 i))) durations)))
|
d@164
|
435 ((= beats (/ 1 (expt 2 i)))
|
d@164
|
436 (setf (gsharp-duration-beams (car durations))
|
d@164
|
437 i)
|
d@164
|
438 (return-from gsharp-durations-from-beats (reverse durations))))))
|
d@164
|
439
|
d@164
|
440 ;;;;;;;;;;;;;;;;;;;;;
|
d@164
|
441 ;;
|
d@164
|
442 ;; Other utility functions
|
d@164
|
443
|
d@164
|
444 (defgeneric gsharp-layer-string (event)
|
d@164
|
445 (:method (e) (name-from-channel-and-patch e))
|
d@168
|
446 (:method ((e amuse-gsharp::gsharp-object))
|
d@168
|
447 (name-from-layer e))
|
d@164
|
448 (:documentation "Return a string that uniquely identifies the layer
|
d@164
|
449 to which event belongs"))
|
d@164
|
450
|
d@168
|
451 (defun name-from-layer (event)
|
d@168
|
452 (let* ((layers (gsharp::layers
|
d@168
|
453 (car
|
d@168
|
454 (gsharp::segments
|
d@168
|
455 (gsharp::buffer
|
d@168
|
456 (gsharp::staff
|
d@168
|
457 (amuse-gsharp::note event)))))))
|
d@168
|
458 (layer (gsharp::layer
|
d@168
|
459 (gsharp::slice
|
d@168
|
460 (gsharp::bar
|
d@168
|
461 (gsharp::cluster
|
d@168
|
462 (amuse-gsharp::note event))))))
|
d@168
|
463 (name (gsharp::name layer))
|
d@168
|
464 (count))
|
d@168
|
465 (dolist (cand-layer layers)
|
d@168
|
466 (if (eq cand-layer layer)
|
d@168
|
467 (if count
|
d@168
|
468 (return-from name-from-layer (concatenate 'string
|
d@168
|
469 name
|
d@168
|
470 (princ-to-string count)))
|
d@168
|
471 (return-from name-from-layer name))
|
d@168
|
472 (when (string= name (gsharp::name cand-layer))
|
d@168
|
473 (setf count (if count (+ count 1) 2)))))))
|
d@168
|
474
|
d@164
|
475 (defun name-from-channel-and-patch (event)
|
d@164
|
476 "Generate layer-identifying string from the patch and channel that
|
d@164
|
477 would be used for midi export. For MIDI, this is guaranteed to
|
d@164
|
478 separate or over-separate. Tracks would possibly be better, but ?don't
|
d@164
|
479 exist in MIDI type 0?"
|
d@164
|
480 (format nil "~D/~D"
|
d@164
|
481 (get-patch-for-midi event)
|
d@164
|
482 (get-channel-for-midi event)))
|
d@164
|
483
|
d@164
|
484 (defun bar-starts (time-signature-list &key (crotchet 1))
|
d@164
|
485 (loop for time-signature in time-signature-list
|
d@164
|
486 nconc (loop for i from (timepoint (onset time-signature))
|
d@164
|
487 to (1- (timepoint (cut-off time-signature)))
|
d@164
|
488 by (* (crotchets-in-a-bar time-signature) crotchet)
|
d@164
|
489 collect i)))
|
d@166
|
490
|
d@166
|
491 (defun beat-starts (time-signature-list &key (crotchet 1))
|
d@168
|
492 (if time-signature-list
|
d@168
|
493 (loop for time-signature in time-signature-list
|
d@168
|
494 nconc (loop for i from (timepoint (onset time-signature))
|
d@168
|
495 to (1- (timepoint (cut-off time-signature)))
|
d@168
|
496 by (* (crotchets-in-a-bar time-signature) crotchet)
|
d@168
|
497 collect (loop for j from 0
|
d@166
|
498 to (* (crotchets-in-a-bar time-signature)
|
d@166
|
499 crotchet)
|
d@166
|
500 by (* (tactus-duration time-signature)
|
d@166
|
501 crotchet)
|
d@168
|
502 collect (+ j i))))
|
d@168
|
503 ;; FIXME: fudge
|
d@168
|
504 (loop for i from 0 to 1000 by 4
|
d@168
|
505 collect (loop for j from i to (+ i 3)
|
d@168
|
506 collect j))))
|
d@166
|
507
|
d@166
|
508 ;;;;;;;;;;;;;;;;;;
|
d@166
|
509 ;;
|
d@166
|
510 ;; Sequence and data structure functions
|
d@166
|
511
|
d@166
|
512
|
d@166
|
513 (defun add-bar-starts-if-not-present (bar-starts changes)
|
d@166
|
514 "Takes a list of bar-start times and one of sounding notes at
|
d@166
|
515 times. If a bar has no change of sounding notes at its start, it would
|
d@166
|
516 not appear in the latter list, but since we will want notes tied over
|
d@166
|
517 barlines, we must add it and return the modified list"
|
d@166
|
518 (let ((new-changes))
|
d@166
|
519 (dolist (event changes (reverse new-changes))
|
d@166
|
520 (do ()
|
d@166
|
521 ((not (and bar-starts
|
d@166
|
522 (< (car bar-starts)
|
d@166
|
523 (car event)))))
|
d@166
|
524 (setf new-changes
|
d@166
|
525 (cons (if (cadr new-changes)
|
d@166
|
526 (cons (car bar-starts)
|
d@166
|
527 (cdar new-changes))
|
d@166
|
528 (list (car bar-starts)))
|
d@166
|
529 new-changes)
|
d@166
|
530 bar-starts (cdr bar-starts)))
|
d@166
|
531 (setf new-changes (cons event new-changes))
|
d@166
|
532 (when (and bar-starts (= (car event) (car bar-starts)))
|
d@166
|
533 (setf bar-starts (cdr bar-starts))))))
|
d@166
|
534
|
d@166
|
535 (defun add-on-off-pair (event data)
|
d@166
|
536 "For walking through an ordered event sequence and building up a
|
d@166
|
537 list of changes to sounding pitches, this function takes an event and
|
d@166
|
538 adds the time for which it sounds to the structure."
|
d@166
|
539 (let ((copied-data)
|
d@166
|
540 (on (* (round
|
d@166
|
541 (* (timepoint event)
|
d@166
|
542 (duration (crotchet event)))
|
d@166
|
543 *rounding-factor*) *rounding-factor*))
|
d@166
|
544 (off (* (round
|
d@166
|
545 (* (timepoint (cut-off event))
|
d@166
|
546 (duration (crotchet event)))
|
d@166
|
547 *rounding-factor*) *rounding-factor*)))
|
d@166
|
548 (do ((data data (cdr data)))
|
d@166
|
549 ((null data) (reverse (cons (list off)
|
d@166
|
550 (cons (list on event)
|
d@166
|
551 copied-data))))
|
d@166
|
552 (cond
|
d@166
|
553 ((<= on (caar data))
|
d@166
|
554 (when (< on (caar data))
|
d@166
|
555 (push (cons on (cons event (cdr (car copied-data))))
|
d@166
|
556 copied-data))
|
d@166
|
557 (do ((data data (cdr data)))
|
d@166
|
558 ((null data)
|
d@166
|
559 (return-from add-on-off-pair
|
d@166
|
560 (reverse (cons (cons off (cddr (car copied-data)))
|
d@166
|
561 copied-data))))
|
d@166
|
562 (cond
|
d@166
|
563 ((= (caar data) off)
|
d@166
|
564 (return-from add-on-off-pair
|
d@166
|
565 (nconc (reverse copied-data) data)))
|
d@166
|
566 ((> (caar data) off)
|
d@166
|
567 (push (cons off (cddr (car copied-data)))
|
d@166
|
568 copied-data)
|
d@166
|
569 (return-from add-on-off-pair
|
d@166
|
570 (nconc (reverse copied-data) data)))
|
d@166
|
571 ((< (caar data) off)
|
d@166
|
572 (push (cons (caar data)
|
d@166
|
573 (cons event (cdr (car data))))
|
d@166
|
574 copied-data)))))
|
d@166
|
575 (t
|
d@166
|
576 (push (car data) copied-data))))))
|
d@166
|
577
|
d@166
|
578 (defun check-ons (ons)
|
d@166
|
579 "looks for small rests such as might be created by midi performance
|
d@166
|
580 of tenuto lines"
|
d@166
|
581 (let ((new-ons))
|
d@166
|
582 (do ((ons ons (cdr ons)))
|
d@166
|
583 ((null (cdr ons))
|
d@166
|
584 (reverse (cons (car ons) new-ons)))
|
d@168
|
585 (unless
|
d@168
|
586 (and (<= (- (car (second ons))
|
d@168
|
587 (car (first ons)))
|
d@168
|
588 *rounding-factor*)
|
d@168
|
589 (null (cdr (first ons))))
|
d@168
|
590 (if (= (- (car (second ons))
|
d@168
|
591 (car (first ons)))
|
d@168
|
592 0)
|
d@168
|
593 (push (cons (caar ons) (remove-duplicates (nconc (cdr (first ons))
|
d@168
|
594 (cdr (second ons)))))
|
d@168
|
595 new-ons)
|
d@168
|
596 (push (car ons) new-ons))))))
|
d@166
|
597
|
d@166
|
598 (defun guess-rounding-factor (events)
|
d@166
|
599 "Assuming that only durations need quantising, look at the lcd for
|
d@166
|
600 onsets"
|
d@166
|
601 (let ((times (map 'list #'(lambda (x)
|
d@166
|
602 (denominator (* (timepoint x)
|
d@166
|
603 (duration (crotchet x)))))
|
d@166
|
604 events)))
|
d@166
|
605 (/ 1 (apply #'lcm times)))) |