annotate tools/gsharp-output.lisp @ 167:4cb3ec07831f

Finish moving get-applicable from amuse-geerdes darcs-hash:20080101132215-40ec0-ce1cb48a47fc7a5a7e2e51ebf0d74d572771c7fb.gz
author d.lewis <d.lewis@gold.ac.uk>
date Tue, 01 Jan 2008 13:22:15 +0000
parents db4acf840bf0
children f1d0ea63581c
rev   line source
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))))