annotate tools/gsharp-output.lisp @ 183:5b2d0e5a99f1

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