annotate tools/gsharp-output.lisp @ 204:10d47e78a53d

Added support for most significant midi messages (some remain missing, but these do not occur in a 1000-file test sample)
author David Lewis <d.lewis@gold.ac.uk>
date Fri, 17 Sep 2010 10:28:27 +0100
parents 3e7b33ae3a0d
children
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@164 68 (defun write-gsharp-eps (composition pathname)
d@164 69 ;; write a score eps from a composition. Most of this can be copied
d@164 70 ;; straight (this is copied already from CSR's code)
d@164 71 ;; Boilerplate stuff:
david@202 72 (let* ((frame (clim:make-application-frame 'gsharp::gsharp))
d@164 73 (clim:*application-frame* frame)
d@164 74 (esa:*esa-instance* frame))
d@164 75 (clim:adopt-frame (clim:find-frame-manager :server-path '(:null)) frame)
d@164 76 (clim:execute-frame-command frame '(gsharp::com-new-buffer))
d@164 77 ;; Now generate the buffer
d@185 78 (make-objects-for-gsharp-buffer composition (car (esa:buffers frame)))
d@185 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@164 84 (clim:execute-frame-command
d@185 85 frame `(gsharp::com-print-buffer-to-file ,pathname))
d@185 86 (car (esa:buffers frame))))
d@164 87
david@202 88 (defun gsharp-change-size-but-keep-bounding-box (left-margin right-edge buffer)
david@202 89 (declare (ignorable buffer))
david@202 90 (setf gsharp-buffer::*default-left-margin* left-margin
david@202 91 (gsharp-buffer::left-margin buffer) left-margin
david@202 92 gsharp-buffer::*default-right-edge* right-edge
david@202 93 (gsharp-buffer::right-edge buffer) right-edge
david@202 94 gsharp::*scale* (/ (+ left-margin right-edge) 900)
david@202 95 gsharp::*top-margin* (/ 80 gsharp::*scale*)))
david@202 96
david@202 97 (defun prepare-gsharp-for-single-system-stuff (buffer)
david@202 98 (gsharp-change-size-but-keep-bounding-box 5 1100 buffer)
david@202 99 (setf gsharp-measure::*staves-per-page* (length (gsharp::staves buffer))))
david@202 100
david@202 101 (defun write-gsharp-ps-single-system (composition pathname)
david@202 102 ;; write a score eps from a composition. Most of this can be copied
david@202 103 ;; straight (this is copied already from CSR's code)
david@202 104 ;; Boilerplate stuff:
david@202 105 (let* ((frame (clim:make-application-frame 'gsharp::gsharp))
david@202 106 (clim:*application-frame* frame)
david@202 107 (esa:*esa-instance* frame))
david@202 108 (clim:adopt-frame (clim:find-frame-manager :server-path '(:null)) frame)
david@202 109 (clim:execute-frame-command frame '(gsharp::com-new-buffer))
david@202 110 ;; Now generate the buffer
david@202 111 (make-objects-for-gsharp-buffer composition (car (esa:buffers frame)))
david@202 112 ;; (fill-gsharp-buffer-with-constituent (car (esa:buffers frame)) composition)
david@202 113 ;; Refresh and process
david@202 114 (setf (gsharp::modified-p (car (esa:buffers frame))) t)
david@202 115 (gsharp::recompute-measures (car (esa:buffers frame)))
david@202 116 (prepare-gsharp-for-single-system-stuff (car (esa:buffers frame)))
david@202 117 (gsharp::recompute-measures (car (esa:buffers frame)))
david@202 118 ;; Print
david@202 119 (clim:execute-frame-command
david@202 120 frame `(gsharp::com-print-buffer-to-file ,pathname))
david@202 121 (car (esa:buffers frame))))
david@202 122
david@202 123 (defun gsharp-preview (composition)
david@202 124 ;; see above for origin of this code
david@202 125 (let* ((frame (clim:make-application-frame 'gsharp::gsharp-minimal))
david@202 126 (clim:*application-frame* frame)
david@202 127 (esa:*esa-instance* frame))
david@202 128 (clim:adopt-frame (clim:find-frame-manager :server-path '(:clx)) frame)
david@202 129 (clim:execute-frame-command frame '(gsharp::com-new-buffer))
david@202 130 ;; Now generate the buffer
david@202 131 (make-objects-for-gsharp-buffer composition (car (esa:buffers frame)))
david@202 132 ;; (fill-gsharp-buffer-with-constituent (car (esa:buffers frame)) composition)
david@202 133 ;; make views, cursors, input states, etc.
david@202 134 (let ((view (make-instance 'gsharp::orchestra-view
david@202 135 :buffer (car (esa:buffers frame))
david@202 136 :cursor (gsharp::make-initial-cursor
david@202 137 (car (esa:buffers frame))))))
david@202 138 (push view (gsharp::views gsharp::*application-frame*))
david@202 139 (setf (gsharp::view (car (gsharp::windows gsharp::*application-frame*))) view
david@202 140 (gsharp::input-state gsharp::*application-frame*) (gsharp::make-input-state)))
david@202 141 ;; Refresh and process
david@202 142 (setf (gsharp::modified-p (car (esa:buffers frame))) t)
david@202 143 (gsharp::recompute-measures (car (esa:buffers frame)))
david@202 144 (gsharp::update-page-numbers frame)
david@202 145 #+nil
david@202 146 (clim:redisplay-frame-panes frame)
david@202 147 (clim:run-frame-top-level frame)))
david@202 148
david@202 149 (defparameter *composition-event-maps* (make-hash-table :test 'eq))
david@202 150
d@164 151 ;;;;;;;;;;;;;;;;;;;;;;;;
d@164 152 ;;
d@164 153 ;; Big `walking through data structures' type functions
d@164 154
d@185 155 (defun make-objects-for-gsharp-buffer (composition buffer)
d@185 156 "This replacement for fill-gsharp-buffer-with-constituent generates
d@185 157 staves and layers itself and attaches them to a segment in the
d@185 158 supplied buffer. Clefs are guessed rather than being read. Each
d@185 159 event is asked for its staff and layer using gsharp-staff-string
d@185 160 gsharp-layer-string, and from this, the connections are
d@185 161 made. gsharp-staff-string defaults to calling gsharp-layer-string,
d@185 162 so in most cases there will be a single staff for each layer. Events
d@185 163 are added, and then staves are sorted using staff<. A proper layout
d@185 164 object would be another way of doing this."
david@202 165 (multiple-value-bind (layer-events layer-staves)
david@202 166 (%gather-objects-for-gsharp-output composition buffer)
david@202 167 ;; Add events/notes/clusters
d@185 168 (maphash #'(lambda (key events)
d@185 169 (add-music-to-layer key
d@185 170 (reverse events)
d@185 171 (gethash key layer-staves)
d@185 172 composition
d@185 173 (handler-bind ((insufficient-information
d@185 174 #'(lambda (c)
d@185 175 (declare (ignore c))
d@185 176 (invoke-restart 'guess))))
d@185 177 (get-applicable-key-signatures composition composition))))
david@202 178 layer-events)
david@202 179 buffer))
david@202 180
david@202 181 (defun %gather-objects-for-gsharp-output (composition buffer)
david@202 182 (let ((layer-names (make-hash-table :test #'equal))
david@202 183 (layer-events (make-hash-table))
david@202 184 (layer-staves (make-hash-table))
david@202 185 (staff-names (make-hash-table :test #'equal))
david@202 186 (staff-name) (staff) (layer-name) (layer) (staves)
david@202 187 (segment))
david@202 188 (sequence::dosequence (event composition)
david@202 189 ;; can't do percussion parts yet:
david@202 190 (when (pitchedp event)
david@202 191 (setf layer-name (gsharp-layer-string event)
david@202 192 layer (gethash layer-name layer-names)
david@202 193 staff-name (gsharp-staff-string event)
david@202 194 staff (gethash staff-name staff-names))
david@202 195 (unless staff
david@202 196 (setf staff (list (%new-gsharp-staff-for-amuse staff-name buffer)
david@202 197 0 0)
david@202 198 (gethash staff-name staff-names) staff))
david@202 199 ;; Keeping note of whether the mean pitch is above C for clef
david@202 200 ;; guessing.
david@202 201 (setf (third staff) (+ (third staff) 1)
david@202 202 (second staff) (+ (second staff)
david@202 203 (min (floor (midi-pitch-number event) 60) 1)))
david@202 204 ;; Check if layer has happened before, if not make it
david@202 205 (unless layer
david@202 206 (setf layer (%create-and-record-layer layer-name (first staff)
david@202 207 segment buffer layer-names
david@202 208 layer-staves layer-events)
david@202 209 segment (gsharp::segment layer)))
david@202 210 ;; Associate new event with layer
david@202 211 (setf layer-events
david@202 212 (%add-event-to-layer-hash event (first staff)
david@202 213 layer layer-events layer-staves))))
david@202 214 ;; Guess clefs for staves: bass if most pitches are below middle
david@202 215 ;; C, otherwise treble. (yes, I know this is stupid)
david@202 216 (maphash #'(lambda (key val)
david@202 217 (declare (ignore key))
david@202 218 (unless (>= (second val) (/ (third val) 2))
david@202 219 (setf (gsharp::clef (first val)) (gsharp::make-clef :bass))))
david@202 220 staff-names)
david@202 221 ;; gather and sort staves
david@202 222 (maphash #'(lambda (key val)
david@202 223 (declare (ignore key))
david@202 224 (push (car val) staves))
d@185 225 staff-names)
david@202 226 (setf staves (sort staves #'stave<)
david@202 227 (gsharp::staves buffer) staves)
david@202 228 (values layer-events layer-staves)))
david@202 229
david@202 230 (defun %new-gsharp-staff-for-amuse (staff-name buffer)
david@202 231 (let ((staff (gsharp::make-fiveline-staff :name staff-name)))
david@202 232 (setf (gsharp::buffer staff) buffer)
david@202 233 staff))
david@202 234
david@202 235 (defun %add-event-to-layer-hash (event staff layer layer-events layer-staves)
david@202 236 ;; (unless (find staff (gethash layer layer-staves))
david@202 237 (unless (member staff (gethash layer layer-staves))
david@202 238 (push staff (gethash layer layer-staves)))
david@202 239 (push event (gethash layer layer-events))
david@202 240 layer-events)
david@202 241
david@202 242 (defun %create-and-record-layer (name staff segment buffer
david@202 243 layer-names layer-staves layer-events)
david@202 244 ;; create fresh layer called name and add to all necessary objects
david@202 245 (let ((layer (gsharp::make-layer (list staff)
david@202 246 :body (gsharp::make-slice :bars nil)
david@202 247 :name name
david@202 248 :segment segment)))
david@202 249 (if segment
david@202 250 (setf (gsharp::layers segment)
david@202 251 (cons layer (gsharp::layers segment)))
david@202 252 (setf segment (make-instance 'gsharp::segment
david@202 253 :buffer buffer
david@202 254 :layers (list layer))
david@202 255 (gsharp::segment layer) segment
david@202 256 (gsharp::segments buffer) (list segment)))
david@202 257 (setf (gethash name layer-names) layer
david@202 258 (gethash layer layer-staves) nil
david@202 259 (gethash layer layer-events) nil)
david@202 260 layer))
david@202 261
d@185 262 (defgeneric stave< (staff1 staff2)
d@185 263 (:method (s1 s2)
d@185 264 (let* ((clefs '(:treble :bass))
d@185 265 (c1 (gsharp::clef s1))
d@185 266 (c2 (gsharp::clef s2))
d@185 267 (pos1 (position (gsharp::name c1) clefs))
d@185 268 (pos2 (position (gsharp::name c2) clefs)))
d@185 269 (or (< pos1 pos2)
d@185 270 (and (= pos1 pos2)
d@185 271 (< (gsharp::lineno c1)
d@185 272 (gsharp::lineno c2)))
d@185 273 (and (= pos1 pos2)
d@185 274 (= (gsharp::lineno c1)
d@185 275 (gsharp::lineno c2))
d@185 276 (string< (gsharp::name s1)
d@185 277 (gsharp::name s2)))))))
d@164 278
d@185 279 (defun bar-starts-2 (composition)
d@185 280 (let ((starts))
d@185 281 (do ((bar-period (current-bar (make-standard-moment 0) composition)
d@185 282 (current-bar (cut-off bar-period) composition)))
d@185 283 ((time>= (cut-off bar-period) (cut-off composition))
d@185 284 (reverse (cons (timepoint bar-period) starts)))
d@185 285 (push (timepoint bar-period) starts))))
d@185 286 (defun beat-starts-2 (bar-starts composition)
d@185 287 ;; FIXME: improve this
d@185 288 (when (get-applicable-time-signatures composition composition)
d@185 289 (handler-bind
d@185 290 ((insufficient-information
d@185 291 #'(lambda (c)
d@185 292 (declare (ignore c))
d@185 293 (invoke-restart 'use-whole-bar))))
d@185 294 (let ((starts) (current))
d@185 295 (do* ((bars bar-starts)
d@185 296 (beat-period (current-beat (make-standard-moment 0) composition)
d@185 297 (current-beat (cut-off beat-period) composition))
d@185 298 (beat-time (timepoint beat-period) (timepoint beat-period)))
d@185 299 ((time>= (cut-off beat-period) (cut-off composition))
d@185 300 (progn
d@185 301 (when (and (cdr bars)
d@185 302 (>= beat-time (second bars)))
d@185 303 (push (reverse current) starts)
d@185 304 (setf current nil
d@185 305 bars (cdr bars)))
d@185 306 (push beat-time current)
d@185 307 (reverse (cons (reverse current) starts))))
d@185 308 (when (and (cdr bars)
d@185 309 (>= beat-time (second bars)))
d@185 310 (push (reverse current) starts)
d@185 311 (setf current nil
d@185 312 bars (cdr bars)))
d@185 313 (push beat-time current))))))
david@202 314
david@202 315 (defgeneric add-music-to-layer (layer events staves composition key-sigs)
david@202 316 (:documentation "Creating all the musical objects for the gsharp staves in the
david@202 317 provided layer"))
david@202 318 ;; change this into some sort of quantize-please mixin? or switch? or something
david@202 319 (defmethod add-music-to-layer (layer events staves (composition amuse-midi::unquantized-composition) key-sigs)
d@185 320 (let* ((bar-moments (bar-starts-2 composition))
d@185 321 (beat-moments (or (beat-starts-2 bar-moments composition)
david@202 322 (mapcar #'list bar-moments)))
d@185 323 (body (gsharp::body layer))
d@185 324 (bar-no 0)
d@185 325 (ons) (position) (clusters) (bar))
d@166 326 ;; this is a cheat to guess timing rounding (quantisation) based
d@166 327 ;; on onset times - only affects midi-like data where onsets are
d@166 328 ;; already rounded, but durations are not (as in TC's fantasia
d@166 329 ;; midi files....)
david@202 330 (setf *rounding-factor* (max (guess-rounding-factor-smart events)
d@166 331 1/8))
d@166 332 ;; First create a list of change-points for when events are
d@166 333 ;; sounding, of the format (time event event event) (time event))
d@166 334 (dolist (event events)
d@166 335 (setf ons (add-on-off-pair event ons)))
d@166 336 ;; These durations may span bars, which is an absolute ban for
d@166 337 ;; most music (Mensurstrich aside), so insert bar starts if not
d@166 338 ;; present. Note that, since the events themselves are recorded in
d@166 339 ;; the list, the existence of ties shuold be detected.
d@185 340 (when bar-moments
d@185 341 (setf ons (add-bar-starts-if-not-present bar-moments ons)))
d@166 342 ;; Finally, one problem here is that, in midi, there is often a
d@166 343 ;; gap or overlap between consecutive notes or chords. Since
d@166 344 ;; rounding happens, but there is no check for bar length here or
d@166 345 ;; within g-sharp, this should verify that everything makes
d@166 346 ;; sense. At the moment, it just removes short rests...
david@202 347 (setf ons (check-ons-2 ons bar-moments))
d@166 348 ;; Now create the bars and the gsharp clusters
d@185 349 (when key-sigs
d@185 350 (dolist (staff staves)
d@185 351 (setf (gsharp::keysig staff)
d@185 352 (make-gsharp-key-signature (car key-sigs) staff))))
d@166 353 (do ((old-ons nil ons)
d@166 354 (ons ons (cdr ons)))
david@202 355 ((null (car ons)))
d@185 356 (when (member (caar ons) bar-moments)
d@177 357 ;; We're at the beginning of a bar.
d@177 358 (when bar (check-beams bar))
d@166 359 (setf bar (gsharp::make-melody-bar))
d@166 360 (gsharp::add-bar bar body bar-no)
d@177 361 (incf bar-no)
d@177 362 (setf position 0))
d@166 363 ;; A quick check for notes which span beats and don't start at
d@166 364 ;; the beginning of their beats. IMO, this makes them more
d@166 365 ;; likely to require a tie.
d@177 366 (when (and (cdr ons)
d@177 367 (not (member (caar ons)
d@185 368 (car beat-moments)))
d@166 369 (find-if #'(lambda (x) (> x (caar ons)))
d@185 370 (car beat-moments))
d@166 371 (< (find-if #'(lambda (x) (> x (caar ons)))
d@185 372 (car beat-moments))
d@166 373 (car (second ons))))
d@166 374 (setf (cdr ons)
d@166 375 (cons (cons (find-if #'(lambda (x) (> x (caar ons)))
d@185 376 (car beat-moments))
d@166 377 (cdar ons))
d@166 378 (cdr ons))))
d@166 379 ;; Making clusters just from duration removes the ability to
d@166 380 ;; divide notes into easy-to-read tied components based on the
d@166 381 ;; time signature (for example, a note of a tactus beat + a
d@166 382 ;; quaver in 6/8 will be rendered as a minim this way) - that's
d@166 383 ;; why I've taken as much of the metrical logic out and put it
d@166 384 ;; above if there are other straightforward rules, they should,
d@166 385 ;; I think go there.
david@202 386 ;; NB now incorporating JF fix (not via version control)
david@202 387 (if (second ons)
david@202 388 (if (cdar ons)
david@202 389 (setf clusters (make-gsharp-clusters-with-duration (- (car (second ons))
david@202 390 (car (car ons)))))
david@202 391 (setf clusters (make-gsharp-rests-with-duration (- (car (second ons))
david@202 392 (car (car ons)))
david@202 393 layer)))
david@202 394 (if (cdar ons)
david@202 395 (setf clusters (make-gsharp-clusters-with-duration (duration (cadar ons))))
david@202 396 (setf clusters (make-gsharp-rests-with-duration (- (timepoint
david@202 397 (cut-off composition))
david@202 398 (caar ons))
david@202 399 layer))))
d@177 400 (let ((now (caar ons)) (first-p t) (pitches))
d@166 401 (do ((clusters clusters (cdr clusters)))
d@166 402 ((null clusters))
d@185 403 (when (member now (car beat-moments))
d@166 404 (setf (gsharp::lbeams (car clusters)) 0))
d@164 405 ;; This function adds cluster at a specific point in the
d@164 406 ;; bar. It does a lot of other things that are probably a)
d@164 407 ;; not necessary or b) should be within the duration logic
d@164 408 ;; above. Would be good not to rely on it (which is not to
d@164 409 ;; say that it isn't reliable)
d@177 410 (gsharp::add-element (car clusters) bar position)
d@177 411 ;; FIXME: Deleting notes that fall on the same note
d@177 412 ;; name. Stupid thing to do.
d@177 413 (setf pitches (remove-duplicates (mapcar #'(lambda (x)
d@177 414 (cons x (pitch-for-gsharp x composition)))
d@177 415 (cdar ons))
d@177 416 :key #'second :test #'=))
d@177 417 (dolist (pitch pitches)
d@168 418 (with-simple-restart (ignore "Ignore note")
d@177 419 (gsharp::add-note (car clusters)
d@177 420 (make-instance 'gsharp::note
d@177 421 :pitch (second pitch)
d@177 422 :accidentals (third pitch)
d@185 423 :staff (staff-for-note (car pitch) staves)
d@177 424 :tie-right (if (or (cdr clusters)
d@177 425 (member (car pitch) (second ons)))
d@177 426 t
d@177 427 nil)
david@202 428 :tie-left (if (or first-p
david@202 429 (member (car pitch) (first old-ons)))
david@202 430 t
david@202 431 nil)))))
d@166 432 (incf now (* (gsharp::duration (car clusters)) 4))
d@177 433 (setf first-p nil)
d@177 434 (incf position)))
d@185 435 (when (and (cdr bar-moments)
d@166 436 (= (car (second ons))
d@185 437 (second bar-moments)))
d@185 438 (setf bar-moments (cdr bar-moments)
d@185 439 beat-moments (cdr beat-moments))))))
d@185 440
david@202 441 (defclass amuse-gsharp-note (gsharp::note)
david@202 442 ((composition :initarg :composition
david@202 443 :accessor composition)
david@202 444 (event :initarg :event
david@202 445 :accessor event)
david@202 446 (groups :initarg :groups
david@202 447 :accessor groups)))
david@202 448
david@202 449 (defun make-amuse-gsharp-note (event staves composition
david@202 450 &key tie-right tie-left groups)
david@202 451 (destructuring-bind (pitch accidental)
david@202 452 (pitch-for-gsharp event composition)
david@202 453 (let ((note (make-instance 'amuse-gsharp-note
david@202 454 :pitch pitch
david@202 455 :accidentals accidental
david@202 456 :staff (staff-for-note event staves)
david@202 457 :tie-right tie-right
david@202 458 :tie-left tie-left
david@202 459 :composition composition
david@202 460 :event event
david@202 461 :groups groups))
david@202 462 (event-map (get-event-map composition)))
david@202 463 (setf (gethash event event-map) note)
david@202 464 note)))
david@202 465
david@202 466 (defun get-event-map (composition)
david@202 467 (unless (gethash composition *composition-event-maps*)
david@202 468 (setf (gethash composition *composition-event-maps*)
david@202 469 (make-hash-table :test 'eq)))
david@202 470 (gethash composition *composition-event-maps*))
david@202 471
david@202 472 (defun get-gsharp-note (event composition)
david@202 473 (gethash event (get-event-map composition)))
david@202 474
david@202 475 (defmethod add-music-to-layer (layer events staves composition key-sigs)
david@202 476 ;; no beaming yet
david@202 477 (let* ((scale (duration (crotchet composition)))
david@202 478 (times (loop for event in events
david@202 479 collect (timepoint event)
david@202 480 collect (timepoint (cut-off event))))
david@202 481 (event-array)
david@202 482 (bar-no 0) (slice (gsharp::body layer)) (bar)
david@202 483 (position 0) (bar-starts) (clusters))
david@202 484 (do* ((bar-period (current-bar (make-moment 0) composition)
david@202 485 (current-bar (cut-off bar-period) composition))
david@202 486 (bar-start (when bar-period (timepoint bar-period))
david@202 487 (when bar-period (timepoint bar-period))))
david@202 488 ((time>= (cut-off bar-period) (cut-off composition))
david@202 489 (setf bar-starts (reverse bar-starts)))
david@202 490 (push bar-start times)
david@202 491 (push bar-start bar-starts))
david@202 492 (setf times (sort (remove-duplicates times) #'<)
david@202 493 event-array (make-array (list-length times) :initial-element nil))
david@202 494 ;; Create an array of which rhythmic clusters each event belongs
david@202 495 ;; to (notational clusters come later)
david@202 496 (loop for time in times
david@202 497 for i from 0
david@202 498 do (loop for event in events
david@202 499 while (<= (timepoint event) time)
david@202 500 when (> (timepoint (cut-off event)) time)
david@202 501 do (push event (aref event-array i))))
david@202 502 (when (and key-sigs (= (timepoint (first key-sigs)) 0))
david@202 503 (mapcar #'(lambda (x) (set-staff-key-signature (first key-sigs) x))
david@202 504 staves)
david@202 505 (setf key-sigs (cdr key-sigs)))
david@202 506 (do ((times times (cdr times))
david@202 507 (i 0 (1+ i)))
david@202 508 ((not times))
david@202 509 (when (and bar-starts (<= (first bar-starts)
david@202 510 (first times)))
david@202 511 (setf bar (gsharp::make-melody-bar))
david@202 512 (gsharp::add-bar bar slice bar-no)
david@202 513 (incf bar-no)
david@202 514 (setf position 0)
david@202 515 (setf bar-starts (cdr bar-starts)))
david@202 516 (if (aref event-array i)
david@202 517 (setf clusters (make-gsharp-clusters-with-exact-duration
david@202 518 (/ (- (or (second times)
david@202 519 (timepoint (cut-off composition)))
david@202 520 (first times))
david@202 521 scale)))
david@202 522 (setf clusters (make-gsharp-rests-with-exact-duration
david@202 523 (/ (- (or (second times)
david@202 524 (timepoint (cut-off composition)))
david@202 525 (first times))
david@202 526 scale)
david@202 527 layer)))
david@202 528 ;; FIXME: This has possible problem cases for key-sig changes
david@202 529 ;; mid-note and for multi-staff signatures. Fix this when the
david@202 530 ;; AMUSE representation is a bit richer.
david@202 531 (when (and key-sigs (<= (timepoint (first key-sigs))
david@202 532 (first times)))
david@202 533 (gsharp::add-element (make-gsharp-key-signature (car key-sigs)
david@202 534 (gsharp::staff bar))
david@202 535 bar position)
david@202 536 (incf position)
david@202 537 (setf key-sigs (cdr key-sigs)))
david@202 538 (do* ((clusters clusters (cdr clusters))
david@202 539 (cluster (car clusters) (car clusters))
david@202 540 (firstp t nil))
david@202 541 ((not clusters))
david@202 542 (gsharp::add-element cluster bar position)
david@202 543 (incf position)
david@202 544 ;; There will be trouble with same pitch in same layer,
david@202 545 ;; but that's a gsharp bug, not an amuse one
david@202 546 (dolist (event (aref event-array i))
david@202 547 (gsharp::add-note
david@202 548 cluster
david@202 549 (make-amuse-gsharp-note event staves composition
david@202 550 :tie-right (or (cdr clusters)
david@202 551 (and (< (1+ i) (length event-array))
david@202 552 (member event (aref event-array (1+ i)))))
david@202 553 :tie-left (or (not firstp)
david@202 554 (and (> i 0)
david@202 555 (member event (aref event-array (1- i))))))))))))
david@202 556
d@185 557 (defun staff-for-note (event staves)
d@185 558 (find-if #'(lambda (x) (string= (gsharp::name x) (gsharp-staff-string event)))
d@185 559 staves))
d@168 560
d@177 561 (defun check-beams (bar)
d@177 562 (do* ((clusters (gsharp::elements bar) (cdr clusters))
d@177 563 (left) (mid) (right))
d@177 564 ((null (cddr clusters)))
d@177 565 (setf left (first clusters)
d@177 566 mid (second clusters)
d@177 567 right (third clusters))
d@177 568 (unless (or (typep mid 'gsharp::rest)
d@177 569 (= (max (gsharp::rbeams mid)
d@177 570 (gsharp::lbeams mid))
d@177 571 0))
d@177 572 (cond
d@177 573 ((or (typep left 'gsharp::rest)
d@177 574 (= (gsharp::rbeams left) 0))
d@177 575 (setf (gsharp::lbeams mid) 0))
d@177 576 ((or (typep right 'gsharp::rest)
d@177 577 (= (gsharp::lbeams right) 0))
d@177 578 (setf (gsharp::rbeams mid) 0))
d@177 579 ((< (gsharp::rbeams left)
d@177 580 (gsharp::lbeams right))
d@177 581 (setf (gsharp::lbeams mid) (gsharp::rbeams left)))
d@177 582 (t (setf (gsharp::rbeams mid) (gsharp::lbeams right)))))))
d@168 583
david@202 584 (defun set-staff-key-signature (key-sig staff)
david@202 585 (setf (gsharp::keysig staff)
david@202 586 (make-gsharp-key-signature key-sig staff)))
david@202 587
d@185 588 (defgeneric make-gsharp-key-signature (key-signature object))
d@185 589 (defmethod make-gsharp-key-signature ((key-signature standard-key-signature) (layer gsharp-buffer::layer))
d@183 590 (let ((alterations (make-array 7 :initial-element :natural))
d@168 591 (order-of-sharps #(3 0 4 1 5 2 6))
d@168 592 (order-of-flats #(6 2 5 1 4 0 3)))
d@168 593 (if (< (key-signature-sharps key-signature) 0)
d@168 594 (dotimes (index (abs (key-signature-sharps key-signature)))
d@168 595 (setf (elt alterations (elt order-of-flats index)) :flat))
d@168 596 (dotimes (index (key-signature-sharps key-signature))
d@168 597 (setf (elt alterations (elt order-of-sharps index)) :sharp)))
d@168 598 (gsharp-buffer::make-key-signature (car (gsharp::staves layer))
d@168 599 :alterations alterations)))
d@185 600 (defmethod make-gsharp-key-signature ((key-signature standard-key-signature)
d@185 601 (staff gsharp::staff))
d@185 602 (let ((alterations (make-array 7 :initial-element :natural))
d@185 603 (order-of-sharps #(3 0 4 1 5 2 6))
d@185 604 (order-of-flats #(6 2 5 1 4 0 3)))
d@185 605 (if (< (key-signature-sharps key-signature) 0)
d@185 606 (dotimes (index (abs (key-signature-sharps key-signature)))
d@185 607 (setf (elt alterations (elt order-of-flats index)) :flat))
d@185 608 (dotimes (index (key-signature-sharps key-signature))
d@185 609 (setf (elt alterations (elt order-of-sharps index)) :sharp)))
d@185 610 (gsharp-buffer::make-key-signature staff
d@185 611 :alterations alterations)))
d@164 612
d@164 613 ;;;;;;;;;;;;;;;;;;;;;;;
d@164 614 ;;
d@164 615 ;; Information conversion functions
d@164 616
d@164 617 ;; Pitch
d@164 618
d@168 619 (defgeneric pitch-for-gsharp (pitch composition)
d@164 620 (:documentation "Given a pitch object, return a list of gsharp's
d@164 621 pitch number and accidental keyword"))
d@168 622 (defmethod pitch-for-gsharp ((pitch diatonic-pitch) composition)
d@168 623 (declare (ignore composition))
d@164 624 ;; Easy for diatonic pitch, although behaviour for extreme or
d@164 625 ;; fractional alterations is unclear.
d@164 626 (list (1+ (diatonic-pitch-mp pitch))
d@164 627 (case (diatonic-pitch-accidental pitch)
d@164 628 (1 :sharp)
d@164 629 (-1 :flat)
d@164 630 (2 :double-sharp)
d@164 631 (-2 :double-flat)
d@164 632 (0 :natural)
d@164 633 (otherwise (error "gsharp can't handle this pitch")))))
d@168 634 (defmethod pitch-for-gsharp ((pitch chromatic-pitch) composition)
d@168 635 ;; Just go for line-of-fifths proximity spelling, but based on
d@168 636 ;; keysig if present. Could always try to spell it with ps13, but..
d@164 637 (let* ((octave (octave pitch))
d@164 638 (pitch-class (pitch-class pitch))
d@164 639 (diatonic-pitch-number (aref #(0 0 1 2 2 3 3 4 4 5 6 6) pitch-class)))
d@164 640 (list (+ (* 7 octave) diatonic-pitch-number)
d@164 641 (aref #(:natural :sharp ;; C C#
d@164 642 :natural ;; D
d@164 643 :flat :natural ;; Eb E
d@164 644 :natural :sharp ;; F F#
d@164 645 :natural :sharp ;; G G#
d@164 646 :natural ;; A
d@164 647 :flat :natural) ;; Bb B
d@164 648 pitch-class))))
d@168 649 (defmethod pitch-for-gsharp ((event standard-chromatic-pitched-event) composition)
d@168 650 ;; Should probably go for line-of-fifths proximity spelling,
d@168 651 ;; if keysig present, but ps13ing for now.
d@168 652 (let* ((octave (octave event))
d@177 653 (event-pos (position event (ocp-list composition) :key #'car))
d@168 654 (note-sequence (get-spelling-list composition))
d@168 655 (spelling (elt note-sequence event-pos))
d@168 656 (note-name (cdr (assoc (aref (second spelling) 0)
d@168 657 '(("C" . 0) ("D" . 1) ("E" . 2) ("F" . 3)
d@168 658 ("G" . 4) ("A" . 5) ("B" . 6))
d@168 659 :test #'string=)))
d@168 660 (accidental (cdr (assoc (aref (second spelling) 1)
d@168 661 '(("ss" . :double-sharp) ("s" . :sharp) ("n" . :natural)
d@168 662 ("f" . :flat) ("ff" . :double-flat))
d@168 663 :test #'string=))))
d@168 664 (list (+ (* 7 octave) note-name) accidental)))
d@168 665
d@168 666
d@168 667 (defparameter *spelling-cache* (make-hash-table))
d@168 668 (defparameter *ocp-cache* (make-hash-table))
d@168 669
d@168 670 (defun get-spelling-list (composition)
d@168 671 (unless (gethash composition *spelling-cache*)
d@168 672 (setf (gethash composition *spelling-cache*)
d@168 673 (ps13:ps13-new-imp (map 'list #'cdr (get-ocp-list composition))
d@168 674 10 42 nil nil nil)))
d@168 675 (gethash composition *spelling-cache*))
d@168 676
d@168 677 (defun get-ocp-list (composition)
d@168 678 (unless (gethash composition *ocp-cache*)
d@168 679 (setf (gethash composition *ocp-cache*) (ocp-list composition)))
d@168 680 (gethash composition *ocp-cache*))
d@168 681
d@168 682 (defun ocp-list (composition)
d@168 683 (flet ((sorter (x y)
d@168 684 (or (amuse:time< x y)
d@168 685 (and (amuse:time= x y)
d@168 686 (amuse:pitch< x y)))))
d@168 687 (loop for e being each element of composition
d@168 688 if (typep e 'amuse:pitched-event)
d@168 689 collect (cons e (make-array 2 :initial-contents
d@168 690 (list (slot-value e 'amuse::time)
d@168 691 (- (slot-value e 'amuse::number) 21)))) into result
d@168 692 finally (return (sort result #'sorter :key #'car)))))
d@168 693
d@164 694 ;; Time
d@164 695
david@202 696 (defun make-gsharp-clusters-with-exact-duration (duration)
david@202 697 ;; at least for now
david@202 698 (make-gsharp-clusters-with-duration duration))
d@166 699 (defun make-gsharp-clusters-with-duration (duration)
d@166 700 "Returns a list of cluster(s) whose total duration is equal to
d@166 701 duration (which is given in crotchets)"
d@166 702 (let ((new-durations (gsharp-durations-from-beats duration)))
d@166 703 (loop for new-duration in new-durations
d@166 704 collect (gsharp::make-cluster :notehead (gsharp-duration-notehead new-duration)
d@166 705 :lbeams (gsharp-duration-beams new-duration)
d@166 706 :rbeams (gsharp-duration-beams new-duration)
d@166 707 :dots (gsharp-duration-dots new-duration)))))
d@166 708
david@202 709 (defun make-gsharp-rests-with-exact-duration (duration layer)
david@202 710 ;; at least for now
david@202 711 (make-gsharp-rests-with-duration duration layer))
d@166 712 (defun make-gsharp-rests-with-duration (duration layer)
d@166 713 "Returns a list of rest(s) whose total duration is equal to
d@166 714 duration (which is given in crotchets)"
d@166 715 (let ((new-durations (gsharp-durations-from-beats duration)))
d@166 716 (loop for new-duration in new-durations
david@202 717 collect (gsharp::make-rest (car (gsharp::staves layer))
d@166 718 :notehead (gsharp-duration-notehead new-duration)
d@166 719 :lbeams (gsharp-duration-beams new-duration)
d@166 720 :rbeams (gsharp-duration-beams new-duration)
d@166 721 :dots (gsharp-duration-dots new-duration)))))
d@164 722
d@164 723 (defun gsharp-durations-from-beats (beats &optional (durations nil))
d@164 724 ;; Takes a count of crotchets and returns a list of
d@164 725 ;; <gsharp-duration>s that most simply defines the attached. This
d@164 726 ;; is a recursive function that finds the longest simple duration
d@164 727 ;; that fits into beats and, if that leaves a remainder, runs again
d@164 728 ;; on the remainder (until hemi-demi-semi-quavers are reached). It
d@164 729 ;; avoids double dots and is ignorant of time-signature. It will be
d@164 730 ;; replaced. Soon.
d@164 731 ;;; FIXME: Handles quantisation fairly
d@164 732 ;; stupidly. Could be made slightly smarter with simple rounding
d@164 733 ;; (erring on the side of longer durations?)
d@164 734 (assert (>= beats 0))
d@164 735 (push (make-gsharp-duration) durations)
d@164 736 ;; First find the longest simple duration that fits in beats
d@164 737 ;; First with notes > 1 crotchet
d@164 738 (loop for option in '((16 :long) (8 :breve) (4 :whole) (2 :half) (1 :filled))
d@164 739 do (cond
d@166 740 ((= beats (* (car option) 7/4))
d@166 741 (setf (gsharp-duration-notehead (car durations))
d@166 742 (cadr option)
d@166 743 (gsharp-duration-dots (car durations))
d@166 744 2)
d@166 745 (return-from gsharp-durations-from-beats (reverse durations)))
d@164 746 ((= beats (* (car option) 3/2))
d@164 747 (setf (gsharp-duration-notehead (car durations))
d@164 748 (cadr option)
d@164 749 (gsharp-duration-dots (car durations))
d@164 750 1)
d@164 751 (return-from gsharp-durations-from-beats (reverse durations)))
d@164 752 ((> beats (car option))
d@164 753 (setf (gsharp-duration-notehead (car durations))
d@164 754 (cadr option))
d@164 755 (return-from gsharp-durations-from-beats
d@164 756 (gsharp-durations-from-beats (- beats (car option)) durations)))
d@164 757 ((= beats (car option))
d@164 758 (setf (gsharp-duration-notehead (car durations))
d@164 759 (cadr option))
d@164 760 (return-from gsharp-durations-from-beats (reverse durations)))))
d@164 761 (setf (gsharp-duration-notehead (car durations))
d@164 762 :filled)
d@164 763 ;; then with short notes (beams rather than noteheads)
d@164 764 (do ((i 1 (1+ i)))
d@164 765 ((= i 4) ;; means either tuplet, very short note or unquantised data
d@164 766 (reverse durations))
d@164 767 (cond
d@164 768 ((= beats (* (/ 1 (expt 2 i)) 3/2))
d@164 769 (setf (gsharp-duration-beams (car durations))
d@164 770 i
d@164 771 (gsharp-duration-dots (car durations))
d@164 772 1)
d@164 773 (return-from gsharp-durations-from-beats (reverse durations)))
d@164 774 ((> beats (/ 1 (expt 2 i)))
d@164 775 (setf (gsharp-duration-beams (car durations))
d@164 776 i)
d@164 777 (return-from gsharp-durations-from-beats
d@164 778 (gsharp-durations-from-beats (- beats (/ 1 (expt 2 i))) durations)))
d@164 779 ((= beats (/ 1 (expt 2 i)))
d@164 780 (setf (gsharp-duration-beams (car durations))
d@164 781 i)
d@164 782 (return-from gsharp-durations-from-beats (reverse durations))))))
d@164 783
d@164 784 ;;;;;;;;;;;;;;;;;;;;;
d@164 785 ;;
d@164 786 ;; Other utility functions
d@164 787
d@164 788 (defgeneric gsharp-layer-string (event)
d@164 789 (:method (e) (name-from-channel-and-patch e))
d@168 790 (:method ((e amuse-gsharp::gsharp-object))
d@168 791 (name-from-layer e))
d@164 792 (:documentation "Return a string that uniquely identifies the layer
d@164 793 to which event belongs"))
d@164 794
d@185 795 (defgeneric gsharp-staff-string (event)
d@185 796 (:method (e) (gsharp-layer-string e))
d@185 797 (:documentation "Return a string that uniquely identifies the staff
d@185 798 to which event belongs"))
d@185 799
d@168 800 (defun name-from-layer (event)
d@177 801 ;; Uses gsharp layer names. Numbers layers in cases of duplication
d@168 802 (let* ((layers (gsharp::layers
d@168 803 (car
d@168 804 (gsharp::segments
d@168 805 (gsharp::buffer
d@168 806 (gsharp::staff
d@168 807 (amuse-gsharp::note event)))))))
d@168 808 (layer (gsharp::layer
d@168 809 (gsharp::slice
d@168 810 (gsharp::bar
d@168 811 (gsharp::cluster
d@168 812 (amuse-gsharp::note event))))))
d@168 813 (name (gsharp::name layer))
d@168 814 (count))
d@168 815 (dolist (cand-layer layers)
d@168 816 (if (eq cand-layer layer)
d@168 817 (if count
d@168 818 (return-from name-from-layer (concatenate 'string
d@168 819 name
d@168 820 (princ-to-string count)))
d@168 821 (return-from name-from-layer name))
d@168 822 (when (string= name (gsharp::name cand-layer))
d@168 823 (setf count (if count (+ count 1) 2)))))))
d@168 824
d@164 825 (defun name-from-channel-and-patch (event)
d@164 826 "Generate layer-identifying string from the patch and channel that
d@164 827 would be used for midi export. For MIDI, this is guaranteed to
d@164 828 separate or over-separate. Tracks would possibly be better, but ?don't
d@164 829 exist in MIDI type 0?"
d@164 830 (format nil "~D/~D"
d@164 831 (get-patch-for-midi event)
d@164 832 (get-channel-for-midi event)))
d@164 833
d@164 834 (defun bar-starts (time-signature-list &key (crotchet 1))
d@164 835 (loop for time-signature in time-signature-list
d@164 836 nconc (loop for i from (timepoint (onset time-signature))
d@164 837 to (1- (timepoint (cut-off time-signature)))
d@164 838 by (* (crotchets-in-a-bar time-signature) crotchet)
d@164 839 collect i)))
d@166 840
d@166 841 (defun beat-starts (time-signature-list &key (crotchet 1))
d@177 842 ;; provides a list of bars, each a list of beats. If no timesig,
d@177 843 ;; guesses at 4/4
d@177 844 ;; FIXME: This is stupid and should disappear if and when proper
d@177 845 ;; beat methods are implemented.
d@168 846 (if time-signature-list
d@168 847 (loop for time-signature in time-signature-list
d@168 848 nconc (loop for i from (timepoint (onset time-signature))
d@168 849 to (1- (timepoint (cut-off time-signature)))
d@168 850 by (* (crotchets-in-a-bar time-signature) crotchet)
d@168 851 collect (loop for j from 0
d@166 852 to (* (crotchets-in-a-bar time-signature)
d@166 853 crotchet)
d@166 854 by (* (tactus-duration time-signature)
d@166 855 crotchet)
d@168 856 collect (+ j i))))
d@168 857 ;; FIXME: fudge
d@168 858 (loop for i from 0 to 1000 by 4
d@168 859 collect (loop for j from i to (+ i 3)
d@168 860 collect j))))
d@166 861
d@166 862 ;;;;;;;;;;;;;;;;;;
d@166 863 ;;
d@166 864 ;; Sequence and data structure functions
d@166 865
d@166 866
d@166 867 (defun add-bar-starts-if-not-present (bar-starts changes)
d@166 868 "Takes a list of bar-start times and one of sounding notes at
d@166 869 times. If a bar has no change of sounding notes at its start, it would
d@166 870 not appear in the latter list, but since we will want notes tied over
d@166 871 barlines, we must add it and return the modified list"
d@166 872 (let ((new-changes))
d@166 873 (dolist (event changes (reverse new-changes))
d@166 874 (do ()
d@166 875 ((not (and bar-starts
d@166 876 (< (car bar-starts)
d@166 877 (car event)))))
d@166 878 (setf new-changes
d@166 879 (cons (if (cadr new-changes)
d@166 880 (cons (car bar-starts)
d@166 881 (cdar new-changes))
d@166 882 (list (car bar-starts)))
d@166 883 new-changes)
d@166 884 bar-starts (cdr bar-starts)))
d@166 885 (setf new-changes (cons event new-changes))
d@166 886 (when (and bar-starts (= (car event) (car bar-starts)))
d@166 887 (setf bar-starts (cdr bar-starts))))))
d@166 888
d@166 889 (defun add-on-off-pair (event data)
d@166 890 "For walking through an ordered event sequence and building up a
d@166 891 list of changes to sounding pitches, this function takes an event and
d@166 892 adds the time for which it sounds to the structure."
david@202 893 (let* ((copied-data) (rounding (/ *rounding-factor* 2))
david@202 894 (on (* (timepoint event)
david@202 895 (duration (crotchet event)))
david@202 896 #+nil (* (round
d@166 897 (* (timepoint event)
d@166 898 (duration (crotchet event)))
david@202 899 rounding) rounding))
david@202 900 (off (* (round
david@202 901 (* (timepoint (cut-off event))
david@202 902 (duration (crotchet event)))
david@202 903 rounding) rounding)))
d@166 904 (do ((data data (cdr data)))
d@166 905 ((null data) (reverse (cons (list off)
d@166 906 (cons (list on event)
d@166 907 copied-data))))
d@166 908 (cond
d@166 909 ((<= on (caar data))
d@166 910 (when (< on (caar data))
d@166 911 (push (cons on (cons event (cdr (car copied-data))))
d@166 912 copied-data))
d@166 913 (do ((data data (cdr data)))
d@166 914 ((null data)
d@166 915 (return-from add-on-off-pair
d@166 916 (reverse (cons (cons off (cddr (car copied-data)))
d@166 917 copied-data))))
d@166 918 (cond
d@166 919 ((= (caar data) off)
d@166 920 (return-from add-on-off-pair
d@166 921 (nconc (reverse copied-data) data)))
d@166 922 ((> (caar data) off)
d@166 923 (push (cons off (cddr (car copied-data)))
d@166 924 copied-data)
d@166 925 (return-from add-on-off-pair
d@166 926 (nconc (reverse copied-data) data)))
d@166 927 ((< (caar data) off)
d@166 928 (push (cons (caar data)
d@166 929 (cons event (cdr (car data))))
d@166 930 copied-data)))))
d@166 931 (t
d@166 932 (push (car data) copied-data))))))
d@166 933
d@177 934 (defun check-ons (ons bar-starts)
d@177 935 "looks for small rests such as might be created by midi performance
d@177 936 of tenuto lines"
d@177 937 (let ((time (caar ons)) (notes (cdar ons))(new-ons))
d@177 938 (do ((ons (cdr ons) (cdr ons)))
d@177 939 ((null ons) (reverse new-ons))
d@177 940 (if (or (member (caar ons) bar-starts)
d@177 941 (> (- (caar ons) time)
d@177 942 *rounding-factor*))
d@177 943 (setf new-ons (cons (cons time notes) new-ons)
d@177 944 time (caar ons)
d@177 945 notes (cdar ons))
d@177 946 (dolist (note (cdar ons))
d@177 947 (unless (member note notes)
d@177 948 (push note notes)))))))
d@177 949
david@202 950 (defun check-ons-2 (ons bar-starts)
david@202 951 "looks for small rests such as might be created by midi performance
david@202 952 of tenuto lines"
david@202 953 (let ((best-time) (found-bar) (new-ons))
david@202 954 (do* ((ons ons (cdr ons))
david@202 955 (on1 (first ons) (first ons))
david@202 956 (on2 (second ons) (second ons))
david@202 957 (query nil nil))
david@202 958 ((null on2) (reverse new-ons))
david@202 959 (unless found-bar
david@202 960 (cond
david@202 961 ((member (first on1) bar-starts)
david@202 962 (setf found-bar t
david@202 963 best-time (first on1)))
david@202 964 ((or (not best-time)
david@202 965 (better-timep (first on1) best-time)) ;; this ought to know about tactus
david@202 966 (setf best-time (first on1)))))
david@202 967 (when (>= (- (first on2) (first on1))
david@202 968 *rounding-factor*)
david@202 969 (push (cons best-time (cdr on1)) new-ons)
david@202 970 (setf best-time nil
david@202 971 found-bar nil)))))
david@202 972
david@202 973 (defun better-timep (t1 t2)
david@202 974 (< (or (granularity t1 4)
david@202 975 (granularity (/ (round t1 1/16) 16) 4))
david@202 976 (or (granularity t2 4)
david@202 977 (granularity (/ (round t2 1/16) 16) 4))))
david@202 978
david@202 979 (defun granularity (n &optional (max 16))
david@202 980 (loop for i from 1 to max
david@202 981 when (= (rem n (expt 2 (- i))) 0)
david@202 982 do (return-from granularity i)))
david@202 983
d@177 984 #+nil
d@177 985
d@166 986 (defun check-ons (ons)
d@166 987 "looks for small rests such as might be created by midi performance
d@166 988 of tenuto lines"
d@177 989 (let ((new-ons) (skip))
d@166 990 (do ((ons ons (cdr ons)))
d@166 991 ((null (cdr ons))
d@177 992 (if skip (reverse new-ons) (reverse (cons (car ons) new-ons))))
d@177 993 (cond
d@177 994 (skip (setf skip nil))
d@177 995 ((not (and (<= (- (car (second ons))
d@177 996 (car (first ons)))
d@177 997 *rounding-factor*)
d@177 998 (null (cdr (first ons)))))
d@177 999 (if (<= (- (car (second ons))
d@177 1000 (car (first ons)))
d@177 1001 *rounding-factor*)
d@177 1002 (progn
d@177 1003 (push (cons (caar ons) (remove-duplicates (nconc (cdr (first ons))
d@177 1004 (cdr (second ons)))))
d@177 1005 new-ons)
d@177 1006 (setf skip t))
d@177 1007 (push (car ons) new-ons)))))))
d@177 1008
d@166 1009
d@166 1010 (defun guess-rounding-factor (events)
d@166 1011 "Assuming that only durations need quantising, look at the lcd for
d@166 1012 onsets"
d@177 1013 (let ((rounding-factor (/ 1 *rounding-factor*)))
d@177 1014 (when events
d@177 1015 (let ((crotchet (duration (crotchet (car events)))))
d@177 1016 (do ((events events (cdr events)))
d@177 1017 ((null (cdr events)))
d@177 1018 (do ((divisor rounding-factor (* 2 divisor)))
d@177 1019 ((<= (rem (* (timepoint (car events)) crotchet)
d@177 1020 divisor)
d@177 1021 (/ divisor 3))
d@177 1022 (setf rounding-factor divisor))))))
d@177 1023 (/ 1 rounding-factor)))
d@177 1024
d@177 1025 (defun guess-rounding-factor-smart (events)
d@177 1026 "Assuming that only durations need quantising, look at the lcd for
d@177 1027 onsets"
d@166 1028 (let ((times (map 'list #'(lambda (x)
d@166 1029 (denominator (* (timepoint x)
d@166 1030 (duration (crotchet x)))))
d@166 1031 events)))
david@202 1032
d@166 1033 (/ 1 (apply #'lcm times))))