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@166
|
107 time-signatures
|
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@166
|
139 (defun add-bars-and-events-to-layer (layer events time-signatures &key (key-signatures nil))
|
d@164
|
140 "Given list of events to be attached to a layer, along with
|
d@164
|
141 applicable time signatures, clumsily waddle through them all and
|
d@164
|
142 slap an approximation to the events into place. Improve this."
|
d@164
|
143 (let* ((crotchet-beats (duration (crotchet (car events)))) ;; (or make into semibreves?)
|
d@166
|
144 (beat-starts (beat-starts time-signatures
|
d@166
|
145 :crotchet crotchet-beats))
|
d@166
|
146 (bar-starts (mapcar #'car beat-starts))
|
d@166
|
147 (ons)
|
d@166
|
148 (clusters) (bar) (bar-no 0)
|
d@164
|
149 (body (gsharp::body layer)))
|
d@166
|
150 ;; this is a cheat to guess timing rounding (quantisation) based
|
d@166
|
151 ;; on onset times - only affects midi-like data where onsets are
|
d@166
|
152 ;; already rounded, but durations are not (as in TC's fantasia
|
d@166
|
153 ;; midi files....)
|
d@166
|
154 (setf *rounding-factor* (max (guess-rounding-factor events)
|
d@166
|
155 1/8))
|
d@166
|
156 ;; First create a list of change-points for when events are
|
d@166
|
157 ;; sounding, of the format (time event event event) (time event))
|
d@166
|
158 (dolist (event events)
|
d@166
|
159 (setf ons (add-on-off-pair event ons)))
|
d@166
|
160 ;; These durations may span bars, which is an absolute ban for
|
d@166
|
161 ;; most music (Mensurstrich aside), so insert bar starts if not
|
d@166
|
162 ;; present. Note that, since the events themselves are recorded in
|
d@166
|
163 ;; the list, the existence of ties shuold be detected.
|
d@166
|
164 (when bar-starts
|
d@166
|
165 (setf ons (add-bar-starts-if-not-present bar-starts ons)))
|
d@166
|
166 ;; Finally, one problem here is that, in midi, there is often a
|
d@166
|
167 ;; gap or overlap between consecutive notes or chords. Since
|
d@166
|
168 ;; rounding happens, but there is no check for bar length here or
|
d@166
|
169 ;; within g-sharp, this should verify that everything makes
|
d@166
|
170 ;; sense. At the moment, it just removes short rests...
|
d@166
|
171 (setf ons (check-ons ons))
|
d@166
|
172 ;; Now create the bars and the gsharp clusters
|
d@166
|
173 (do ((old-ons nil ons)
|
d@166
|
174 (ons ons (cdr ons)))
|
d@166
|
175 ((null (cdr ons)))
|
d@166
|
176 (when (member (caar ons) bar-starts)
|
d@166
|
177 (setf bar (gsharp::make-melody-bar))
|
d@166
|
178 (gsharp::add-bar bar body bar-no)
|
d@166
|
179 (incf bar-no))
|
d@166
|
180 ;; A quick check for notes which span beats and don't start at
|
d@166
|
181 ;; the beginning of their beats. IMO, this makes them more
|
d@166
|
182 ;; likely to require a tie.
|
d@166
|
183 (when (and (not (member (caar ons)
|
d@166
|
184 (car beat-starts)))
|
d@166
|
185 (find-if #'(lambda (x) (> x (caar ons)))
|
d@166
|
186 (car beat-starts))
|
d@166
|
187 (< (find-if #'(lambda (x) (> x (caar ons)))
|
d@166
|
188 (car beat-starts))
|
d@166
|
189 (car (second ons))))
|
d@166
|
190 (setf (cdr ons)
|
d@166
|
191 (cons (cons (find-if #'(lambda (x) (> x (caar ons)))
|
d@166
|
192 (car beat-starts))
|
d@166
|
193 (cdar ons))
|
d@166
|
194 (cdr ons))))
|
d@166
|
195 ;; Making clusters just from duration removes the ability to
|
d@166
|
196 ;; divide notes into easy-to-read tied components based on the
|
d@166
|
197 ;; time signature (for example, a note of a tactus beat + a
|
d@166
|
198 ;; quaver in 6/8 will be rendered as a minim this way) - that's
|
d@166
|
199 ;; why I've taken as much of the metrical logic out and put it
|
d@166
|
200 ;; above if there are other straightforward rules, they should,
|
d@166
|
201 ;; I think go there.
|
d@166
|
202 (if (cdr (car ons))
|
d@166
|
203 (setf clusters (make-gsharp-clusters-with-duration (- (car (second ons))
|
d@166
|
204 (car (car ons)))))
|
d@166
|
205 (setf clusters (make-gsharp-rests-with-duration (- (car (second ons))
|
d@166
|
206 (car (car ons)))
|
d@166
|
207 layer)))
|
d@166
|
208 (let ((now (caar ons)) (first-p t))
|
d@166
|
209 (do ((clusters clusters (cdr clusters)))
|
d@166
|
210 ((null clusters))
|
d@166
|
211 (when (member now (car beat-starts))
|
d@166
|
212 (setf (gsharp::lbeams (car clusters)) 0))
|
d@164
|
213 ;; This function adds cluster at a specific point in the
|
d@164
|
214 ;; bar. It does a lot of other things that are probably a)
|
d@164
|
215 ;; not necessary or b) should be within the duration logic
|
d@164
|
216 ;; above. Would be good not to rely on it (which is not to
|
d@164
|
217 ;; say that it isn't reliable)
|
d@166
|
218 (gsharp-mxml::add-element-at-duration (car clusters)
|
d@166
|
219 bar
|
d@166
|
220 (/ (- now (car bar-starts))
|
d@166
|
221 4))
|
d@166
|
222 (dolist (note (cdr (car ons)))
|
d@166
|
223 (when note
|
d@166
|
224 (let ((pitch (pitch-for-gsharp note)))
|
d@166
|
225 (gsharp::add-note (car clusters)
|
d@166
|
226 (make-instance 'gsharp::note
|
d@164
|
227 :pitch (first pitch)
|
d@164
|
228 :accidentals (second pitch)
|
d@166
|
229 :staff (car (gsharp::staves layer))
|
d@166
|
230 :tie-right (if (cdr clusters)
|
d@166
|
231 t
|
d@166
|
232 (member note (second ons)))
|
d@166
|
233 :tie-left (if first-p
|
d@166
|
234 (member note (first old-ons))
|
d@166
|
235 t))))))
|
d@166
|
236 (incf now (* (gsharp::duration (car clusters)) 4))
|
d@166
|
237 (setf first-p nil)))
|
d@166
|
238 (when (and (cdr bar-starts)
|
d@166
|
239 (= (car (second ons))
|
d@166
|
240 (second bar-starts)))
|
d@166
|
241 (setf bar-starts (cdr bar-starts)
|
d@166
|
242 beat-starts (cdr beat-starts))))
|
d@166
|
243 (dolist (key-signature key-signatures)
|
d@166
|
244 ;; code half-inched from mxml.lisp (maybe there's a useful
|
d@166
|
245 ;; function to be abstracted here?), but I don't really
|
d@166
|
246 ;; understand how key changes work...?
|
d@166
|
247 (let ((alterations (make-array 7))
|
d@166
|
248 (order-of-sharps #(3 0 4 1 5 2 6))
|
d@166
|
249 (order-of-flats #(6 2 5 1 4 0 3)))
|
d@166
|
250 (if (< (key-signature-sharps key-signature) 0)
|
d@166
|
251 (dotimes (index (abs (key-signature-sharps key-signature)))
|
d@166
|
252 (setf (elt alterations (elt order-of-flats index)) :flat))
|
d@166
|
253 (dotimes (index (key-signature-sharps key-signature))
|
d@166
|
254 (setf (elt alterations (elt order-of-sharps index)) :sharp)))
|
d@166
|
255 (setf (gsharp::keysig (car (gsharp::staves layer)))
|
d@166
|
256 (gsharp-buffer::make-key-signature (car (gsharp::staves layer))
|
d@166
|
257 :alteration alterations))))))
|
d@164
|
258
|
d@164
|
259 ;;;;;;;;;;;;;;;;;;;;;;;
|
d@164
|
260 ;;
|
d@164
|
261 ;; Information conversion functions
|
d@164
|
262
|
d@164
|
263 ;; Pitch
|
d@164
|
264
|
d@164
|
265 (defgeneric pitch-for-gsharp (pitch)
|
d@164
|
266 (:documentation "Given a pitch object, return a list of gsharp's
|
d@164
|
267 pitch number and accidental keyword"))
|
d@164
|
268 (defmethod pitch-for-gsharp ((pitch diatonic-pitch))
|
d@164
|
269 ;; Easy for diatonic pitch, although behaviour for extreme or
|
d@164
|
270 ;; fractional alterations is unclear.
|
d@164
|
271 (list (1+ (diatonic-pitch-mp pitch))
|
d@164
|
272 (case (diatonic-pitch-accidental pitch)
|
d@164
|
273 (1 :sharp)
|
d@164
|
274 (-1 :flat)
|
d@164
|
275 (2 :double-sharp)
|
d@164
|
276 (-2 :double-flat)
|
d@164
|
277 (0 :natural)
|
d@164
|
278 (otherwise (error "gsharp can't handle this pitch")))))
|
d@164
|
279 (defmethod pitch-for-gsharp ((pitch chromatic-pitch))
|
d@164
|
280 ;; Just go for line-of-fifths proximity spelling. Could always try
|
d@164
|
281 ;; to spell it, but...
|
d@164
|
282 (let* ((octave (octave pitch))
|
d@164
|
283 (pitch-class (pitch-class pitch))
|
d@164
|
284 (diatonic-pitch-number (aref #(0 0 1 2 2 3 3 4 4 5 6 6) pitch-class)))
|
d@164
|
285 (list (+ (* 7 octave) diatonic-pitch-number)
|
d@164
|
286 (aref #(:natural :sharp ;; C C#
|
d@164
|
287 :natural ;; D
|
d@164
|
288 :flat :natural ;; Eb E
|
d@164
|
289 :natural :sharp ;; F F#
|
d@164
|
290 :natural :sharp ;; G G#
|
d@164
|
291 :natural ;; A
|
d@164
|
292 :flat :natural) ;; Bb B
|
d@164
|
293 pitch-class))))
|
d@164
|
294
|
d@164
|
295 ;; Time
|
d@164
|
296
|
d@166
|
297 (defun make-gsharp-clusters-with-duration (duration)
|
d@166
|
298 "Returns a list of cluster(s) whose total duration is equal to
|
d@166
|
299 duration (which is given in crotchets)"
|
d@166
|
300 (let ((new-durations (gsharp-durations-from-beats duration)))
|
d@166
|
301 (loop for new-duration in new-durations
|
d@166
|
302 collect (gsharp::make-cluster :notehead (gsharp-duration-notehead new-duration)
|
d@166
|
303 :lbeams (gsharp-duration-beams new-duration)
|
d@166
|
304 :rbeams (gsharp-duration-beams new-duration)
|
d@166
|
305 :dots (gsharp-duration-dots new-duration)))))
|
d@166
|
306
|
d@166
|
307 (defun make-gsharp-rests-with-duration (duration layer)
|
d@166
|
308 "Returns a list of rest(s) whose total duration is equal to
|
d@166
|
309 duration (which is given in crotchets)"
|
d@166
|
310 (let ((new-durations (gsharp-durations-from-beats duration)))
|
d@166
|
311 (loop for new-duration in new-durations
|
d@166
|
312 collect(gsharp::make-rest (car (gsharp::staves layer))
|
d@166
|
313 :notehead (gsharp-duration-notehead new-duration)
|
d@166
|
314 :lbeams (gsharp-duration-beams new-duration)
|
d@166
|
315 :rbeams (gsharp-duration-beams new-duration)
|
d@166
|
316 :dots (gsharp-duration-dots new-duration)))))
|
d@164
|
317
|
d@164
|
318 (defun gsharp-durations-from-beats (beats &optional (durations nil))
|
d@164
|
319 ;; Takes a count of crotchets and returns a list of
|
d@164
|
320 ;; <gsharp-duration>s that most simply defines the attached. This
|
d@164
|
321 ;; is a recursive function that finds the longest simple duration
|
d@164
|
322 ;; that fits into beats and, if that leaves a remainder, runs again
|
d@164
|
323 ;; on the remainder (until hemi-demi-semi-quavers are reached). It
|
d@164
|
324 ;; avoids double dots and is ignorant of time-signature. It will be
|
d@164
|
325 ;; replaced. Soon.
|
d@164
|
326 ;;; FIXME: Handles quantisation fairly
|
d@164
|
327 ;; stupidly. Could be made slightly smarter with simple rounding
|
d@164
|
328 ;; (erring on the side of longer durations?)
|
d@164
|
329 (assert (>= beats 0))
|
d@164
|
330 (push (make-gsharp-duration) durations)
|
d@164
|
331 ;; First find the longest simple duration that fits in beats
|
d@164
|
332 ;; First with notes > 1 crotchet
|
d@164
|
333 (loop for option in '((16 :long) (8 :breve) (4 :whole) (2 :half) (1 :filled))
|
d@164
|
334 do (cond
|
d@166
|
335 ((= beats (* (car option) 7/4))
|
d@166
|
336 (setf (gsharp-duration-notehead (car durations))
|
d@166
|
337 (cadr option)
|
d@166
|
338 (gsharp-duration-dots (car durations))
|
d@166
|
339 2)
|
d@166
|
340 (return-from gsharp-durations-from-beats (reverse durations)))
|
d@164
|
341 ((= beats (* (car option) 3/2))
|
d@164
|
342 (setf (gsharp-duration-notehead (car durations))
|
d@164
|
343 (cadr option)
|
d@164
|
344 (gsharp-duration-dots (car durations))
|
d@164
|
345 1)
|
d@164
|
346 (return-from gsharp-durations-from-beats (reverse durations)))
|
d@164
|
347 ((> beats (car option))
|
d@164
|
348 (setf (gsharp-duration-notehead (car durations))
|
d@164
|
349 (cadr option))
|
d@164
|
350 (return-from gsharp-durations-from-beats
|
d@164
|
351 (gsharp-durations-from-beats (- beats (car option)) durations)))
|
d@164
|
352 ((= beats (car option))
|
d@164
|
353 (setf (gsharp-duration-notehead (car durations))
|
d@164
|
354 (cadr option))
|
d@164
|
355 (return-from gsharp-durations-from-beats (reverse durations)))))
|
d@164
|
356 (setf (gsharp-duration-notehead (car durations))
|
d@164
|
357 :filled)
|
d@164
|
358 ;; then with short notes (beams rather than noteheads)
|
d@164
|
359 (do ((i 1 (1+ i)))
|
d@164
|
360 ((= i 4) ;; means either tuplet, very short note or unquantised data
|
d@164
|
361 (reverse durations))
|
d@164
|
362 (cond
|
d@164
|
363 ((= beats (* (/ 1 (expt 2 i)) 3/2))
|
d@164
|
364 (setf (gsharp-duration-beams (car durations))
|
d@164
|
365 i
|
d@164
|
366 (gsharp-duration-dots (car durations))
|
d@164
|
367 1)
|
d@164
|
368 (return-from gsharp-durations-from-beats (reverse durations)))
|
d@164
|
369 ((> beats (/ 1 (expt 2 i)))
|
d@164
|
370 (setf (gsharp-duration-beams (car durations))
|
d@164
|
371 i)
|
d@164
|
372 (return-from gsharp-durations-from-beats
|
d@164
|
373 (gsharp-durations-from-beats (- beats (/ 1 (expt 2 i))) durations)))
|
d@164
|
374 ((= beats (/ 1 (expt 2 i)))
|
d@164
|
375 (setf (gsharp-duration-beams (car durations))
|
d@164
|
376 i)
|
d@164
|
377 (return-from gsharp-durations-from-beats (reverse durations))))))
|
d@164
|
378
|
d@164
|
379 ;;;;;;;;;;;;;;;;;;;;;
|
d@164
|
380 ;;
|
d@164
|
381 ;; Other utility functions
|
d@164
|
382
|
d@164
|
383 (defgeneric gsharp-layer-string (event)
|
d@164
|
384 (:method (e) (name-from-channel-and-patch e))
|
d@164
|
385 (:documentation "Return a string that uniquely identifies the layer
|
d@164
|
386 to which event belongs"))
|
d@164
|
387
|
d@164
|
388 (defun name-from-channel-and-patch (event)
|
d@164
|
389 "Generate layer-identifying string from the patch and channel that
|
d@164
|
390 would be used for midi export. For MIDI, this is guaranteed to
|
d@164
|
391 separate or over-separate. Tracks would possibly be better, but ?don't
|
d@164
|
392 exist in MIDI type 0?"
|
d@164
|
393 (format nil "~D/~D"
|
d@164
|
394 (get-patch-for-midi event)
|
d@164
|
395 (get-channel-for-midi event)))
|
d@164
|
396
|
d@164
|
397 (defun bar-starts (time-signature-list &key (crotchet 1))
|
d@164
|
398 (loop for time-signature in time-signature-list
|
d@164
|
399 nconc (loop for i from (timepoint (onset time-signature))
|
d@164
|
400 to (1- (timepoint (cut-off time-signature)))
|
d@164
|
401 by (* (crotchets-in-a-bar time-signature) crotchet)
|
d@164
|
402 collect i)))
|
d@166
|
403
|
d@166
|
404 (defun beat-starts (time-signature-list &key (crotchet 1))
|
d@166
|
405 (loop for time-signature in time-signature-list
|
d@166
|
406 nconc (loop for i from (timepoint (onset time-signature))
|
d@166
|
407 to (1- (timepoint (cut-off time-signature)))
|
d@166
|
408 by (* (crotchets-in-a-bar time-signature) crotchet)
|
d@166
|
409 collect (loop for j from 0
|
d@166
|
410 to (* (crotchets-in-a-bar time-signature)
|
d@166
|
411 crotchet)
|
d@166
|
412 by (* (tactus-duration time-signature)
|
d@166
|
413 crotchet)
|
d@166
|
414 collect (+ j i)))))
|
d@166
|
415
|
d@166
|
416 ;;;;;;;;;;;;;;;;;;
|
d@166
|
417 ;;
|
d@166
|
418 ;; Sequence and data structure functions
|
d@166
|
419
|
d@166
|
420
|
d@166
|
421 (defun add-bar-starts-if-not-present (bar-starts changes)
|
d@166
|
422 "Takes a list of bar-start times and one of sounding notes at
|
d@166
|
423 times. If a bar has no change of sounding notes at its start, it would
|
d@166
|
424 not appear in the latter list, but since we will want notes tied over
|
d@166
|
425 barlines, we must add it and return the modified list"
|
d@166
|
426 (let ((new-changes))
|
d@166
|
427 (dolist (event changes (reverse new-changes))
|
d@166
|
428 (do ()
|
d@166
|
429 ((not (and bar-starts
|
d@166
|
430 (< (car bar-starts)
|
d@166
|
431 (car event)))))
|
d@166
|
432 (setf new-changes
|
d@166
|
433 (cons (if (cadr new-changes)
|
d@166
|
434 (cons (car bar-starts)
|
d@166
|
435 (cdar new-changes))
|
d@166
|
436 (list (car bar-starts)))
|
d@166
|
437 new-changes)
|
d@166
|
438 bar-starts (cdr bar-starts)))
|
d@166
|
439 (setf new-changes (cons event new-changes))
|
d@166
|
440 (when (and bar-starts (= (car event) (car bar-starts)))
|
d@166
|
441 (setf bar-starts (cdr bar-starts))))))
|
d@166
|
442
|
d@166
|
443 (defun add-on-off-pair (event data)
|
d@166
|
444 "For walking through an ordered event sequence and building up a
|
d@166
|
445 list of changes to sounding pitches, this function takes an event and
|
d@166
|
446 adds the time for which it sounds to the structure."
|
d@166
|
447 (let ((copied-data)
|
d@166
|
448 (on (* (round
|
d@166
|
449 (* (timepoint event)
|
d@166
|
450 (duration (crotchet event)))
|
d@166
|
451 *rounding-factor*) *rounding-factor*))
|
d@166
|
452 (off (* (round
|
d@166
|
453 (* (timepoint (cut-off event))
|
d@166
|
454 (duration (crotchet event)))
|
d@166
|
455 *rounding-factor*) *rounding-factor*)))
|
d@166
|
456 (do ((data data (cdr data)))
|
d@166
|
457 ((null data) (reverse (cons (list off)
|
d@166
|
458 (cons (list on event)
|
d@166
|
459 copied-data))))
|
d@166
|
460 (cond
|
d@166
|
461 ((<= on (caar data))
|
d@166
|
462 (when (< on (caar data))
|
d@166
|
463 (push (cons on (cons event (cdr (car copied-data))))
|
d@166
|
464 copied-data))
|
d@166
|
465 (do ((data data (cdr data)))
|
d@166
|
466 ((null data)
|
d@166
|
467 (return-from add-on-off-pair
|
d@166
|
468 (reverse (cons (cons off (cddr (car copied-data)))
|
d@166
|
469 copied-data))))
|
d@166
|
470 (cond
|
d@166
|
471 ((= (caar data) off)
|
d@166
|
472 (return-from add-on-off-pair
|
d@166
|
473 (nconc (reverse copied-data) data)))
|
d@166
|
474 ((> (caar data) off)
|
d@166
|
475 (push (cons off (cddr (car copied-data)))
|
d@166
|
476 copied-data)
|
d@166
|
477 (return-from add-on-off-pair
|
d@166
|
478 (nconc (reverse copied-data) data)))
|
d@166
|
479 ((< (caar data) off)
|
d@166
|
480 (push (cons (caar data)
|
d@166
|
481 (cons event (cdr (car data))))
|
d@166
|
482 copied-data)))))
|
d@166
|
483 (t
|
d@166
|
484 (push (car data) copied-data))))))
|
d@166
|
485
|
d@166
|
486 (defun check-ons (ons)
|
d@166
|
487 "looks for small rests such as might be created by midi performance
|
d@166
|
488 of tenuto lines"
|
d@166
|
489 (let ((new-ons))
|
d@166
|
490 (do ((ons ons (cdr ons)))
|
d@166
|
491 ((null (cdr ons))
|
d@166
|
492 (reverse (cons (car ons) new-ons)))
|
d@166
|
493 (unless (and (<= (- (car (second ons))
|
d@166
|
494 (car (first ons)))
|
d@166
|
495 *rounding-factor*)
|
d@166
|
496 (null (cdr (first ons))))
|
d@166
|
497 (push (car ons) new-ons)))))
|
d@166
|
498
|
d@166
|
499 (defun guess-rounding-factor (events)
|
d@166
|
500 "Assuming that only durations need quantising, look at the lcd for
|
d@166
|
501 onsets"
|
d@166
|
502 (let ((times (map 'list #'(lambda (x)
|
d@166
|
503 (denominator (* (timepoint x)
|
d@166
|
504 (duration (crotchet x)))))
|
d@166
|
505 events)))
|
d@166
|
506 (/ 1 (apply #'lcm times)))) |