Mercurial > hg > amuse
changeset 166:db4acf840bf0
Better score rendering with (potentially) key-signature and over-barline ties
darcs-hash:20080101124212-40ec0-b68021c3de57a29df8db45b82a0a099d2aa6f775.gz
author | d.lewis <d.lewis@gold.ac.uk> |
---|---|
date | Tue, 01 Jan 2008 12:42:12 +0000 |
parents | f59787f1101e |
children | 4cb3ec07831f |
files | implementations/gsharp/gsharp-import.lisp tools/gsharp-output.lisp |
diffstat | 2 files changed, 244 insertions(+), 75 deletions(-) [+] |
line wrap: on
line diff
--- a/implementations/gsharp/gsharp-import.lisp Tue Jan 01 12:39:30 2008 +0000 +++ b/implementations/gsharp/gsharp-import.lisp Tue Jan 01 12:42:12 2008 +0000 @@ -40,7 +40,7 @@ (mapcar (lambda (note) (make-instance 'gsharp-pitched-event :note note - :slice-index index + :slice-index index :number (gsharp-play::midi-pitch note) :time time :interval (* 4 (compute-duration note))))
--- a/tools/gsharp-output.lisp Tue Jan 01 12:39:30 2008 +0000 +++ b/tools/gsharp-output.lisp Tue Jan 01 12:42:12 2008 +0000 @@ -42,6 +42,8 @@ (in-package "AMUSE-TOOLS") +(defparameter *rounding-factor* 1/4) + (defstruct gsharp-duration "Structure for specifying duration-related parameters for g-sharp" notehead (beams 0) (dots 0) (tied-p nil)) @@ -80,6 +82,7 @@ ;; FIXME: Throughout this, I assume that ;; get-applicable-time-signatures isn't '() (let ((time-signatures (get-applicable-time-signatures composition composition)) + (key-signatures (get-applicable-key-signatures composition composition)) (layers)) (multiple-value-bind (layer-events layer-scores) ;; Get hash-tables of events by layer and counts of events @@ -100,7 +103,9 @@ :name name) layers) ;; Add the notes and bars - (add-bars-and-events (car layers) (reverse events) time-signatures))) + (add-bars-and-events-to-layer (car layers) (reverse events) + time-signatures + :key-signatures key-signatures))) layer-events) ;; Attach layers to a segment and place segment into buffer (let* ((segment (make-instance 'gsharp::segment :layers layers :buffer buffer))) @@ -131,87 +136,125 @@ (if (< (midi-pitch-number event) 60) 1 0)))))))) -(defun add-bars-and-events (layer events time-signatures) +(defun add-bars-and-events-to-layer (layer events time-signatures &key (key-signatures nil)) "Given list of events to be attached to a layer, along with applicable time signatures, clumsily waddle through them all and slap an approximation to the events into place. Improve this." (let* ((crotchet-beats (duration (crotchet (car events)))) ;; (or make into semibreves?) - (bar-starts (bar-starts time-signatures :crotchet crotchet-beats)) - (cluster) (bar (gsharp::make-melody-bar)) (cluster-time) (bar-no 0) + (beat-starts (beat-starts time-signatures + :crotchet crotchet-beats)) + (bar-starts (mapcar #'car beat-starts)) + (ons) + (clusters) (bar) (bar-no 0) (body (gsharp::body layer))) - (gsharp::add-bar bar body bar-no) - (do* ((events events (cdr events)) - (event (car events) (car events))) - ((null events) layer) - (when (and (cdr bar-starts) - (>= (timepoint event) (second bar-starts))) - ;; new bar - and add 0 or more empty bars - (dotimes (i (- (or (position-if #'(lambda (x) (> x (timepoint event))) - bar-starts) - 2) - 2)) - ;; This only runs if one or more whole-bar rests is needed - ;; in a layer. - (incf bar-no) - (setf bar (gsharp::make-melody-bar)) - (gsharp::add-bar bar body bar-no) - (let* ((new-duration (quick-gs-duration (if (cdr bar-starts) - (- (second bar-starts) - (first bar-starts)) - 4))) - (rest (gsharp::make-rest (car (gsharp::staves layer)) - :notehead (gsharp-duration-notehead new-duration) - :lbeams (gsharp-duration-beams new-duration) - :rbeams (gsharp-duration-beams new-duration) - :dots (gsharp-duration-dots new-duration)))) - (gsharp-mxml::add-element-at-duration rest bar 0)) - (setf bar-starts (cdr bar-starts))) - ;; Move on to new bar - (setf bar (gsharp::make-melody-bar) - bar-starts (cdr bar-starts)) - (incf bar-no) - (gsharp::add-bar bar body bar-no)) - (when (or (not cluster-time) - (not (time= cluster-time (onset event)))) - ;; Not part of a pre-existant chord (this will have more - ;; complicated logic when I add ties). Create a new cluster. - (let* ((beat-duration (/ (duration event) crotchet-beats)) - ;; This hideous thing gives a duration that doesn't - ;; overlap with the next note or barline. Unneccessary - ;; when I fix ties and start supplying bar info to - ;; duration function. - (new-duration (quick-gs-duration (min beat-duration - (if (or (not (cdr events)) - (time= (second events) - event)) - beat-duration - (/ (- (timepoint (second events)) - (timepoint event)) - crotchet-beats)) - (if (cdr bar-starts) - (- (second bar-starts) - (/ (timepoint event) - crotchet-beats)) - beat-duration))))) - (setf cluster (gsharp::make-cluster :notehead (gsharp-duration-notehead new-duration) - :lbeams (gsharp-duration-beams new-duration) - :rbeams (gsharp-duration-beams new-duration) - :dots (gsharp-duration-dots new-duration))) + ;; this is a cheat to guess timing rounding (quantisation) based + ;; on onset times - only affects midi-like data where onsets are + ;; already rounded, but durations are not (as in TC's fantasia + ;; midi files....) + (setf *rounding-factor* (max (guess-rounding-factor events) + 1/8)) + ;; First create a list of change-points for when events are + ;; sounding, of the format (time event event event) (time event)) + (dolist (event events) + (setf ons (add-on-off-pair event ons))) + ;; These durations may span bars, which is an absolute ban for + ;; most music (Mensurstrich aside), so insert bar starts if not + ;; present. Note that, since the events themselves are recorded in + ;; the list, the existence of ties shuold be detected. + (when bar-starts + (setf ons (add-bar-starts-if-not-present bar-starts ons))) + ;; Finally, one problem here is that, in midi, there is often a + ;; gap or overlap between consecutive notes or chords. Since + ;; rounding happens, but there is no check for bar length here or + ;; within g-sharp, this should verify that everything makes + ;; sense. At the moment, it just removes short rests... + (setf ons (check-ons ons)) + ;; Now create the bars and the gsharp clusters + (do ((old-ons nil ons) + (ons ons (cdr ons))) + ((null (cdr ons))) + (when (member (caar ons) bar-starts) + (setf bar (gsharp::make-melody-bar)) + (gsharp::add-bar bar body bar-no) + (incf bar-no)) + ;; A quick check for notes which span beats and don't start at + ;; the beginning of their beats. IMO, this makes them more + ;; likely to require a tie. + (when (and (not (member (caar ons) + (car beat-starts))) + (find-if #'(lambda (x) (> x (caar ons))) + (car beat-starts)) + (< (find-if #'(lambda (x) (> x (caar ons))) + (car beat-starts)) + (car (second ons)))) + (setf (cdr ons) + (cons (cons (find-if #'(lambda (x) (> x (caar ons))) + (car beat-starts)) + (cdar ons)) + (cdr ons)))) + ;; Making clusters just from duration removes the ability to + ;; divide notes into easy-to-read tied components based on the + ;; time signature (for example, a note of a tactus beat + a + ;; quaver in 6/8 will be rendered as a minim this way) - that's + ;; why I've taken as much of the metrical logic out and put it + ;; above if there are other straightforward rules, they should, + ;; I think go there. + (if (cdr (car ons)) + (setf clusters (make-gsharp-clusters-with-duration (- (car (second ons)) + (car (car ons))))) + (setf clusters (make-gsharp-rests-with-duration (- (car (second ons)) + (car (car ons))) + layer))) + (let ((now (caar ons)) (first-p t)) + (do ((clusters clusters (cdr clusters))) + ((null clusters)) + (when (member now (car beat-starts)) + (setf (gsharp::lbeams (car clusters)) 0)) ;; This function adds cluster at a specific point in the ;; bar. It does a lot of other things that are probably a) ;; not necessary or b) should be within the duration logic ;; above. Would be good not to rely on it (which is not to ;; say that it isn't reliable) - (gsharp-mxml::add-element-at-duration cluster bar (/ (- (/ (timepoint event) - crotchet-beats) - (car bar-starts)) - 4)) - (setf cluster-time (onset event)))) - (let ((pitch (pitch-for-gsharp event))) - (gsharp::add-note cluster (make-instance 'gsharp::note + (gsharp-mxml::add-element-at-duration (car clusters) + bar + (/ (- now (car bar-starts)) + 4)) + (dolist (note (cdr (car ons))) + (when note + (let ((pitch (pitch-for-gsharp note))) + (gsharp::add-note (car clusters) + (make-instance 'gsharp::note :pitch (first pitch) :accidentals (second pitch) - :staff (car (gsharp::staves layer)))))))) + :staff (car (gsharp::staves layer)) + :tie-right (if (cdr clusters) + t + (member note (second ons))) + :tie-left (if first-p + (member note (first old-ons)) + t)))))) + (incf now (* (gsharp::duration (car clusters)) 4)) + (setf first-p nil))) + (when (and (cdr bar-starts) + (= (car (second ons)) + (second bar-starts))) + (setf bar-starts (cdr bar-starts) + beat-starts (cdr beat-starts)))) + (dolist (key-signature key-signatures) + ;; code half-inched from mxml.lisp (maybe there's a useful + ;; function to be abstracted here?), but I don't really + ;; understand how key changes work...? + (let ((alterations (make-array 7)) + (order-of-sharps #(3 0 4 1 5 2 6)) + (order-of-flats #(6 2 5 1 4 0 3))) + (if (< (key-signature-sharps key-signature) 0) + (dotimes (index (abs (key-signature-sharps key-signature))) + (setf (elt alterations (elt order-of-flats index)) :flat)) + (dotimes (index (key-signature-sharps key-signature)) + (setf (elt alterations (elt order-of-sharps index)) :sharp))) + (setf (gsharp::keysig (car (gsharp::staves layer))) + (gsharp-buffer::make-key-signature (car (gsharp::staves layer)) + :alteration alterations)))))) ;;;;;;;;;;;;;;;;;;;;;;; ;; @@ -251,10 +294,26 @@ ;; Time -(defun quick-gs-duration (crotchets) - ;; gsharp-durations-from-beats returns a list of durations. Take the - ;; first (= largest). - (car (gsharp-durations-from-beats crotchets))) +(defun make-gsharp-clusters-with-duration (duration) + "Returns a list of cluster(s) whose total duration is equal to + duration (which is given in crotchets)" + (let ((new-durations (gsharp-durations-from-beats duration))) + (loop for new-duration in new-durations + collect (gsharp::make-cluster :notehead (gsharp-duration-notehead new-duration) + :lbeams (gsharp-duration-beams new-duration) + :rbeams (gsharp-duration-beams new-duration) + :dots (gsharp-duration-dots new-duration))))) + +(defun make-gsharp-rests-with-duration (duration layer) + "Returns a list of rest(s) whose total duration is equal to + duration (which is given in crotchets)" + (let ((new-durations (gsharp-durations-from-beats duration))) + (loop for new-duration in new-durations + collect(gsharp::make-rest (car (gsharp::staves layer)) + :notehead (gsharp-duration-notehead new-duration) + :lbeams (gsharp-duration-beams new-duration) + :rbeams (gsharp-duration-beams new-duration) + :dots (gsharp-duration-dots new-duration))))) (defun gsharp-durations-from-beats (beats &optional (durations nil)) ;; Takes a count of crotchets and returns a list of @@ -273,6 +332,12 @@ ;; First with notes > 1 crotchet (loop for option in '((16 :long) (8 :breve) (4 :whole) (2 :half) (1 :filled)) do (cond + ((= beats (* (car option) 7/4)) + (setf (gsharp-duration-notehead (car durations)) + (cadr option) + (gsharp-duration-dots (car durations)) + 2) + (return-from gsharp-durations-from-beats (reverse durations))) ((= beats (* (car option) 3/2)) (setf (gsharp-duration-notehead (car durations)) (cadr option) @@ -335,3 +400,107 @@ to (1- (timepoint (cut-off time-signature))) by (* (crotchets-in-a-bar time-signature) crotchet) collect i))) + +(defun beat-starts (time-signature-list &key (crotchet 1)) + (loop for time-signature in time-signature-list + nconc (loop for i from (timepoint (onset time-signature)) + to (1- (timepoint (cut-off time-signature))) + by (* (crotchets-in-a-bar time-signature) crotchet) + collect (loop for j from 0 + to (* (crotchets-in-a-bar time-signature) + crotchet) + by (* (tactus-duration time-signature) + crotchet) + collect (+ j i))))) + +;;;;;;;;;;;;;;;;;; +;; +;; Sequence and data structure functions + + +(defun add-bar-starts-if-not-present (bar-starts changes) + "Takes a list of bar-start times and one of sounding notes at +times. If a bar has no change of sounding notes at its start, it would +not appear in the latter list, but since we will want notes tied over +barlines, we must add it and return the modified list" + (let ((new-changes)) + (dolist (event changes (reverse new-changes)) + (do () + ((not (and bar-starts + (< (car bar-starts) + (car event))))) + (setf new-changes + (cons (if (cadr new-changes) + (cons (car bar-starts) + (cdar new-changes)) + (list (car bar-starts))) + new-changes) + bar-starts (cdr bar-starts))) + (setf new-changes (cons event new-changes)) + (when (and bar-starts (= (car event) (car bar-starts))) + (setf bar-starts (cdr bar-starts)))))) + +(defun add-on-off-pair (event data) + "For walking through an ordered event sequence and building up a +list of changes to sounding pitches, this function takes an event and +adds the time for which it sounds to the structure." + (let ((copied-data) + (on (* (round + (* (timepoint event) + (duration (crotchet event))) + *rounding-factor*) *rounding-factor*)) + (off (* (round + (* (timepoint (cut-off event)) + (duration (crotchet event))) + *rounding-factor*) *rounding-factor*))) + (do ((data data (cdr data))) + ((null data) (reverse (cons (list off) + (cons (list on event) + copied-data)))) + (cond + ((<= on (caar data)) + (when (< on (caar data)) + (push (cons on (cons event (cdr (car copied-data)))) + copied-data)) + (do ((data data (cdr data))) + ((null data) + (return-from add-on-off-pair + (reverse (cons (cons off (cddr (car copied-data))) + copied-data)))) + (cond + ((= (caar data) off) + (return-from add-on-off-pair + (nconc (reverse copied-data) data))) + ((> (caar data) off) + (push (cons off (cddr (car copied-data))) + copied-data) + (return-from add-on-off-pair + (nconc (reverse copied-data) data))) + ((< (caar data) off) + (push (cons (caar data) + (cons event (cdr (car data)))) + copied-data))))) + (t + (push (car data) copied-data)))))) + +(defun check-ons (ons) + "looks for small rests such as might be created by midi performance +of tenuto lines" + (let ((new-ons)) + (do ((ons ons (cdr ons))) + ((null (cdr ons)) + (reverse (cons (car ons) new-ons))) + (unless (and (<= (- (car (second ons)) + (car (first ons))) + *rounding-factor*) + (null (cdr (first ons)))) + (push (car ons) new-ons))))) + +(defun guess-rounding-factor (events) + "Assuming that only durations need quantising, look at the lcd for +onsets" + (let ((times (map 'list #'(lambda (x) + (denominator (* (timepoint x) + (duration (crotchet x))))) + events))) + (/ 1 (apply #'lcm times)))) \ No newline at end of file