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