annotate tools/gsharp-output.lisp @ 180:1a2b876b5587

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