annotate tools/gsharp-output.lisp @ 171:98443d36ac6a

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