annotate tools/gsharp-output.lisp @ 330:2fbff655ba47 tip

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