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