Reversed version of ISTATE-ROTATE-NOTEHEADS, associated with keystrokes #\i #\g
[gsharp.git] / melody.lisp
blob7cd464f8205a2526715680ce98987d35dbd84a67
1 (in-package :gsharp-buffer)
3 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
4 ;;;
5 ;;; Clef
7 ;;; The line number on which the clef is located on the staff.
8 ;;; The bottom line of the staff is number 1.
9 (defgeneric lineno (clef))
11 ;;; for key signature drawing calcluations. FIXME: in fact the layout
12 ;;; of key signatures isn't the same across all clefs.
13 (defgeneric b-position (clef))
14 (defgeneric f-position (clef))
16 ;;; the note number of the bottom line of this clef.
17 (defgeneric bottom-line (clef))
19 (defclass clef (gsharp-object name-mixin)
20 ((lineno :reader lineno :initarg :lineno
21 :type (or (integer 0 8) null))))
23 (defun make-clef (name &key lineno)
24 (declare (type (member :treble :treble8 :bass :c :percussion) name)
25 (type (or (integer 0 8) null) lineno))
26 (when (null lineno)
27 (setf lineno
28 (ecase name
29 ((:treble :treble8) 2)
30 (:bass 6)
31 (:c 4)
32 (:percussion 3))))
33 (make-instance 'clef :name name :lineno lineno))
35 (defmethod slots-to-be-saved append ((c clef))
36 '(lineno))
38 (defun read-clef-v3 (stream char n)
39 (declare (ignore char n))
40 (apply #'make-instance 'clef (read-delimited-list #\] stream t)))
42 (set-dispatch-macro-character #\[ #\K
43 #'read-clef-v3
44 *gsharp-readtable-v3*)
46 ;;; given a clef, return the staff step of the B that should have
47 ;;; the first flat sign in key signatures with flats
48 (defmethod b-position ((clef clef))
49 (ecase (name clef)
50 (:bass (- (lineno clef) 4))
51 ((:treble :treble8) (+ (lineno clef) 2))
52 (:c (- (lineno clef) 1))))
55 ;;; given a clef, return the staff step of the F that should have
56 ;;; the first sharp sign in key signatures with sharps
57 (defmethod f-position ((clef clef))
58 (ecase (name clef)
59 (:bass (lineno clef))
60 ((:treble :treble8) (+ (lineno clef) 6))
61 (:c (+ (lineno clef) 3))))
63 (defmethod bottom-line ((clef clef))
64 (- (ecase (name clef)
65 (:treble 32)
66 (:bass 24)
67 (:c 28)
68 (:treble8 25))
69 (lineno clef)))
71 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
72 ;;;
73 ;;; Fiveline staff
75 (defgeneric clef (fiveline-staff))
77 (defclass fiveline-staff (staff)
78 ((clef :accessor clef :initarg :clef :initform (make-clef :treble))
79 (%keysig :accessor keysig :initarg :keysig
80 :initform (make-array 7 :initial-element :natural))
81 (staffwise-elements :accessor staffwise-elements :initform nil)))
83 (defgeneric key-signatures (staff)
84 (:method ((s fiveline-staff))
85 (remove-if #'(lambda (x) (not (typep x 'key-signature)))
86 (staffwise-elements s))))
87 (defgeneric time-signatures (staff)
88 (:method ((s fiveline-staff))
89 (remove-if #'(lambda (x) (not (typep x 'time-signature)))
90 (staffwise-elements s))))
92 (defmethod initialize-instance :after ((obj fiveline-staff) &rest args)
93 (declare (ignore args))
94 (with-slots (%keysig) obj
95 (when (vectorp %keysig)
96 (setf %keysig
97 (make-instance 'key-signature :staff obj :alterations %keysig)))))
99 (defun make-fiveline-staff (&rest args &key name clef keysig)
100 (declare (ignore name clef keysig))
101 (apply #'make-instance 'fiveline-staff args))
103 (defmethod slots-to-be-saved append ((s fiveline-staff))
104 '(clef %keysig))
106 (defun read-fiveline-staff-v3 (stream char n)
107 (declare (ignore char n))
108 (apply #'make-instance 'fiveline-staff (read-delimited-list #\] stream t)))
110 (set-dispatch-macro-character #\[ #\=
111 #'read-fiveline-staff-v3
112 *gsharp-readtable-v3*)
114 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
116 ;;; Note
118 ;;; Notes are immutable objets. If you want to alter (say) the staff
119 ;;; or the pitch of a note, you have to delete it and add a new note
120 ;;; with the right characteristics.
122 ;;; Return the pitch of the note.
123 (defgeneric pitch (note))
125 ;;; Return the accidentals of the note. The value returned is one of
126 ;;; :natural :flat :double-flat :sharp or :double-sharp.
127 (defgeneric accidentals (note))
129 ;;; Return a non-negative integer indicating the number of dots of the
130 ;;; note. The value nil is returned whenever the note takes its
131 ;;; number of dots from the cluster to which it belongs.
132 (defgeneric dots (note))
134 ;;; Returns the cluster to which the note belongs, or nil if the note
135 ;;; currently does not belong to any cluster.
136 (defgeneric cluster (note))
138 ;;; The pitch is a number from 0 to 128
139 ;;;
140 ;;; The staff is a staff object.
141 ;;;
142 ;;; Head can be :long, :breve, :whole, :half, :filled, or nil. A
143 ;;; value of nil means that the notehead is determined by that of the
144 ;;; cluster to which the note belongs.
145 ;;;
146 ;;; Accidentals can be :natural :flat :double-flat :sharp or :double-sharp.
147 ;;; The default is :natural. Whether a note is actually displayed
148 ;;; preceded by one of the corresponding signs is a matter of context and
149 ;;; display style.
150 ;;;
151 ;;; The number of dots can be an integer or nil, meaning that the number
152 ;;; of dots is taken from the cluster. The default value is nil.
153 ;;;
154 ;;; The actual duration of the note is computed from the note head, the
155 ;;; number of beams of the cluster to which the note belongs, and the
156 ;;; number of dots in the usual way.
158 (defclass note (gsharp-object)
159 ((cluster :initform nil :initarg :cluster :accessor cluster)
160 (pitch :initarg :pitch :reader pitch :type (integer 0 127))
161 (staff :initarg :staff :reader staff :type staff)
162 (head :initform nil :initarg :head :reader head
163 :type (or (member :long :breve :whole :half :filled) null))
164 (accidentals :initform :natural :initarg :accidentals :reader accidentals
165 ;; FIXME: we want :TYPE ACCIDENTAL here but need to
166 ;; sort out order of definition for that to be useful.
167 #+nil #+nil
168 :type (member :natural :flat :double-flat :sharp :double-sharp))
169 (dots :initform nil :initarg :dots :reader dots
170 :type (or (integer 0 3) null))
171 (%tie-right :initform nil :initarg :tie-right :accessor tie-right)
172 (%tie-left :initform nil :initarg :tie-left :accessor tie-left)))
174 (defun make-note (pitch staff &rest args &key head (accidentals :natural) dots)
175 (declare (type (integer 0 127) pitch)
176 (type staff staff)
177 (type (or (member :long :breve :whole :half :filled) null) head)
178 ;; FIXME: :TYPE ACCIDENTAL
179 #+nil #+nil
180 (type (member :natural :flat :double-flat :sharp :double-sharp)
181 accidentals)
182 (type (or (integer 0 3) null) dots)
183 (ignore head accidentals dots))
184 (apply #'make-instance 'note :pitch pitch :staff staff args))
186 (defmethod slots-to-be-saved append ((n note))
187 '(pitch staff head accidentals dots %tie-right %tie-left))
189 (defun read-note-v3 (stream char n)
190 (declare (ignore char n))
191 (apply #'make-instance 'note (read-delimited-list #\] stream t)))
193 (set-dispatch-macro-character #\[ #\N
194 #'read-note-v3
195 *gsharp-readtable-v3*)
197 ;;; Return true if note1 is considered less than note2.
198 (defun note-less (note1 note2)
199 (< (pitch note1) (pitch note2)))
201 ;;; Return true if note1 is considered equal to note2.
202 (defun note-equal (note1 note2)
203 (= (pitch note1) (pitch note2)))
206 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
208 ;;; Tuning (support for microtonal and historical tunings/temperaments)
210 ;;; FIXME: add name-mixin also?
211 (defclass tuning (gsharp-object)
212 ((master-pitch-note :initform (make-instance 'note :pitch 33 ; a above middle c
213 :staff (make-instance 'staff))
214 :initarg :master-pitch-note
215 :type note
216 :accessor master-pitch-note)
217 (master-pitch-freq :initform 440
218 :initarg :master-pitch-freq
219 :accessor master-pitch-freq)))
221 (defmethod slots-to-be-saved append ((tuning tuning))
222 '(master-pitch-note master-pitch-freq))
224 ;;; Returns how a note should be tuned in a given tuning
225 ;;; in terms of a cent value.
226 (defgeneric note-cents (note tuning))
228 ;;; 12-edo is provided for efficiency only. It is a
229 ;;; special case of a regular temperament. Perhaps it
230 ;;; should be removed?
231 (defclass 12-edo (tuning)
234 (defmethod slots-to-be-saved append ((tuning 12-edo))
235 '())
237 (defmethod note-cents ((note note) (tuning 12-edo))
238 (multiple-value-bind (octave pitch) (floor (pitch note) 7)
239 (+ (* 1200 (1+ octave))
240 (ecase pitch (0 0) (1 200) (2 400) (3 500) (4 700) (5 900) (6 1100))
241 (ecase (accidentals note)
242 (:double-flat -200)
243 (:sesquiflat -150)
244 (:flat -100)
245 (:semiflat -50)
246 (:natural 0)
247 (:semisharp 50)
248 (:sharp 100)
249 (:sesquisharp 150)
250 (:double-sharp 200)))))
252 ;;; regular temperaments are temperaments that
253 ;;; retain their interval sizes regardless of modulation, as opposed to
254 ;;; irregular temperaments.
255 (defclass regular-temperament (tuning)
256 ((octave-cents :initform 1200 :initarg :octave-cents :accessor octave-cents)
257 (fifth-cents :initform 700 :initarg :fifth-cents :accessor fifth-cents)
258 (quartertone-cents :initform 50 :initarg :quartertone-cents :accessor quartertone-cents)
259 ;; TODO: Add cent sizes of various microtonal accidentals, perhaps in an alist?
262 (defmethod slots-to-be-saved append ((tuning regular-temperament))
263 '(octave-cents fifth-cents))
265 (defmethod note-cents ((note note) (tuning regular-temperament))
266 (let ((octaves 1)
267 (fifths 0)
268 (sharps 0) ;; short for 7 fifths up and 4 octaves down
269 (quartertones 0))
270 (incf octaves (floor (pitch note) 7))
271 (ecase (mod (pitch note) 7)
272 (0 (progn))
273 (1 (progn (incf octaves -1) (incf fifths 2)))
274 (2 (progn (incf octaves -2) (incf fifths 4)))
275 (3 (progn (incf octaves 1) (incf fifths -1)))
276 (4 (progn (incf fifths 1)))
277 (5 (progn (incf octaves -1) (incf fifths 3)))
278 (6 (progn (incf octaves -2) (incf fifths 5))))
279 (ecase (accidentals note)
280 (:double-flat (incf sharps -2))
281 (:sesquiflat (incf sharps -1) (incf quartertones -1))
282 (:flat (incf sharps -1))
283 (:semiflat (incf quartertones -1))
284 (:natural)
285 (:semisharp (incf quartertones 1))
286 (:sharp (incf sharps 1))
287 (:sesquisharp (incf sharps 1) (incf quartertones 1))
288 (:double-sharp (incf sharps 2)))
289 (incf octaves (* -4 sharps))
290 (incf fifths (* 7 sharps))
291 (+ (* octaves (octave-cents tuning))
292 (* fifths (fifth-cents tuning))
293 (* quartertones (quartertone-cents tuning)))))
295 ;;; TODO: (defclass irregular-temperament ...)
297 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
299 ;;; Melody element
301 (defclass melody-element (rhythmic-element) ())
303 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
305 ;;; Key signature
307 (defgeneric alterations (key-signature)
308 (:documentation "return the alterations in the form of a
309 7-element array where each element is either :natural,
310 :sharp, or :flat according to how each staff position
311 should be altered"))
313 (defgeneric more-sharps (key-signature &optional n)
314 (:documentation "make the key signature N alterations
315 sharper by removing some flats and/or adding some sharps"))
317 (defgeneric more-flats (key-signature &optional n)
318 (:documentation "make the key signature N alterations
319 flatter by removing some sharps and/or adding some flats"))
321 (defclass staffwise-element (element)
322 ((%staff :initarg :staff :reader staff)))
323 (defmethod slots-to-be-saved append ((s-e staffwise-element))
324 '(%staff))
326 (defclass key-signature (staffwise-element)
327 ((%alterations :initform (make-array 7 :initial-element :natural)
328 :initarg :alterations :reader alterations)))
330 (defun make-key-signature (staff &rest args &key alterations)
331 (declare (type (or null (simple-vector 7)) alterations)
332 (ignore alterations))
333 (apply #'make-instance 'key-signature :staff staff args))
335 (defmethod slots-to-be-saved append ((k key-signature))
336 '(%alterations))
338 (defmethod more-sharps ((sig key-signature) &optional (n 1))
339 (let ((alt (alterations sig)))
340 (loop repeat n
341 do (cond ((eq (aref alt 3) :flat) (setf (aref alt 3) :natural))
342 ((eq (aref alt 0) :flat) (setf (aref alt 0) :natural))
343 ((eq (aref alt 4) :flat) (setf (aref alt 4) :natural))
344 ((eq (aref alt 1) :flat) (setf (aref alt 1) :natural))
345 ((eq (aref alt 5) :flat) (setf (aref alt 5) :natural))
346 ((eq (aref alt 2) :flat) (setf (aref alt 2) :natural))
347 ((eq (aref alt 6) :flat) (setf (aref alt 6) :natural))
348 ((eq (aref alt 3) :natural) (setf (aref alt 3) :sharp))
349 ((eq (aref alt 0) :natural) (setf (aref alt 0) :sharp))
350 ((eq (aref alt 4) :natural) (setf (aref alt 4) :sharp))
351 ((eq (aref alt 1) :natural) (setf (aref alt 1) :sharp))
352 ((eq (aref alt 5) :natural) (setf (aref alt 5) :sharp))
353 ((eq (aref alt 2) :natural) (setf (aref alt 2) :sharp))
354 ((eq (aref alt 6) :natural) (setf (aref alt 6) :sharp))))))
356 (defmethod more-flats ((sig key-signature) &optional (n 1))
357 (let ((alt (alterations sig)))
358 (loop repeat n
359 do (cond ((eq (aref alt 6) :sharp) (setf (aref alt 6) :natural))
360 ((eq (aref alt 2) :sharp) (setf (aref alt 2) :natural))
361 ((eq (aref alt 5) :sharp) (setf (aref alt 5) :natural))
362 ((eq (aref alt 1) :sharp) (setf (aref alt 1) :natural))
363 ((eq (aref alt 4) :sharp) (setf (aref alt 4) :natural))
364 ((eq (aref alt 0) :sharp) (setf (aref alt 0) :natural))
365 ((eq (aref alt 3) :sharp) (setf (aref alt 3) :natural))
366 ((eq (aref alt 6) :natural) (setf (aref alt 6) :flat))
367 ((eq (aref alt 2) :natural) (setf (aref alt 2) :flat))
368 ((eq (aref alt 5) :natural) (setf (aref alt 5) :flat))
369 ((eq (aref alt 1) :natural) (setf (aref alt 1) :flat))
370 ((eq (aref alt 4) :natural) (setf (aref alt 4) :flat))
371 ((eq (aref alt 0) :natural) (setf (aref alt 0) :flat))
372 ((eq (aref alt 3) :natural) (setf (aref alt 3) :flat))))))
374 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
376 ;;; Time signature
377 ;; * no make function (no type checking)
378 ;; * slots-to-be-saved only 'cos it's there
379 ;; * What accessors do we need (if any)?
380 ;; * Should I copy the (keysig) functionality from gui.lisp?
382 (defclass time-signature (staffwise-element)
383 ((%components :initarg :components :reader time-signature-components
384 :initform nil)))
385 (defmethod slots-to-be-saved append ((t-s time-signature))
386 '(%components))
388 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
390 ;;; Cluster
392 ;;; Return a list of the notes of the cluster
393 (defgeneric notes (cluster))
395 ;;; Add a note to the cluster. It is an error if there is already a
396 ;;; note in the cluster with the same staff and the same pitch.
397 (defgeneric add-note (cluster note))
399 ;;; Find a note in a cluster. The comparison is made using only the
400 ;;; pitch of the supplied note. If the note does not exist nil is returned.
401 (defgeneric find-note (cluster note))
403 ;;; Delete a note from the cluster to which it belongs. It is an
404 ;;; error to call this function if the note currently does not belong
405 ;;; to any cluster.
406 (defgeneric remove-note (note))
408 (defclass cluster (melody-element)
409 ((notes :initform '() :initarg :notes :accessor notes)
410 (stem-direction :initform :auto :initarg :stem-direction :accessor stem-direction)))
412 (defmethod initialize-instance :after ((c cluster) &rest args)
413 (declare (ignore args))
414 (loop for note in (notes c)
415 do (setf (cluster note) c)))
417 (defun make-cluster (&rest args
418 &key (notehead :filled) (lbeams 0) (rbeams 0) (dots 0)
419 (xoffset 0) notes (stem-direction :auto))
420 (declare (type (member :long :breve :whole :half :filled) notehead)
421 (type (integer 0 5) lbeams)
422 (type (integer 0 5) rbeams)
423 (type (integer 0 3) dots)
424 (type number xoffset)
425 (type list notes)
426 (type (member :up :down :auto) stem-direction)
427 (ignore notehead lbeams rbeams dots xoffset notes stem-direction))
428 (apply #'make-instance 'cluster args))
430 (defmethod slots-to-be-saved append ((c cluster))
431 '(stem-direction notes))
433 (defun read-cluster-v3 (stream char n)
434 (declare (ignore char n))
435 (apply #'make-instance 'cluster (read-delimited-list #\] stream t)))
437 (set-dispatch-macro-character #\[ #\%
438 #'read-cluster-v3
439 *gsharp-readtable-v3*)
441 (define-condition note-already-in-cluster (gsharp-condition) ()
442 (:report
443 (lambda (condition stream)
444 (declare (ignore condition))
445 (format stream "Attempt to add a note already in a cluster"))))
447 (defmethod add-note ((cluster cluster) (note note))
448 (with-slots (notes) cluster
449 (assert (not (find note notes :test #'note-equal))
451 'note-already-in-cluster)
452 (setf notes (merge 'list notes (list note) #'note-less)
453 (cluster note) cluster)))
455 (defmethod find-note ((cluster cluster) (note note))
456 (with-slots (notes) cluster
457 (car (member (pitch note) notes :key #'pitch))))
459 (define-condition note-not-in-cluster (gsharp-condition) ()
460 (:report
461 (lambda (condition stream)
462 (declare (ignore condition))
463 (format stream "Attempt to delete a note not in a cluster"))))
465 (defmethod remove-note ((note note))
466 (with-slots (cluster) note
467 (assert cluster () 'note-not-in-cluster)
468 (with-slots (notes) cluster
469 (setf notes (delete note notes :test #'eq)))
470 (setf cluster nil)))
472 (defun lower-bound (bound list &key (test #'<))
473 "Return the `largest' element in the sorted list LIST such that
474 \(TEST element BOUND) is true."
475 (let ((last nil))
476 (dolist (item list)
477 (unless (funcall test item bound)
478 (return-from lower-bound last))
479 (setf last item))
480 last))
482 (defmethod cluster-lower-bound ((cluster cluster) (bound note))
483 (with-slots (notes) cluster
484 (lower-bound bound notes :test #'note-less)))
486 (defmethod cluster-upper-bound ((cluster cluster) (bound note))
487 (with-slots (notes) cluster
488 (lower-bound bound (reverse notes) :test (complement #'note-less))))
490 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
492 ;;; Rest
494 (defclass rest (melody-element)
495 ((staff :initarg :staff :reader staff)
496 (staff-pos :initarg :staff-pos :initform 4 :reader staff-pos)))
498 (defun make-rest (staff &rest args
499 &key (staff-pos 4) (notehead :filled) (lbeams 0) (rbeams 0)
500 (dots 0) (xoffset 0))
501 (declare (type staff staff)
502 (type integer staff-pos)
503 (type (member :long :breve :whole :half :filled) notehead)
504 (type (integer 0 5) lbeams)
505 (type (integer 0 5) rbeams)
506 (type (integer 0 3) dots)
507 (type number xoffset)
508 (ignore staff-pos notehead lbeams rbeams dots xoffset))
509 (apply #'make-instance 'rest
510 :staff staff args))
512 (defmethod slots-to-be-saved append ((s rest))
513 '(staff staff-pos))
515 (defun read-rest-v3 (stream char n)
516 (declare (ignore char n))
517 (apply #'make-instance 'rest (read-delimited-list #\] stream t)))
519 (set-dispatch-macro-character #\[ #\-
520 #'read-rest-v3
521 *gsharp-readtable-v3*)
523 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
525 ;;; Melody bar
527 (defclass melody-bar (bar) ())
529 (defun make-melody-bar (&rest args &key elements)
530 (declare (type list elements)
531 (ignore elements))
532 (apply #'make-instance 'melody-bar args))
534 (defmethod make-bar-for-staff ((staff fiveline-staff) &rest args &key elements)
535 (declare (ignore elements))
536 (apply #'make-instance 'melody-bar args))
538 (defun read-melody-bar-v3 (stream char n)
539 (declare (ignore char n))
540 (apply #'make-instance 'melody-bar (read-delimited-list #\] stream t)))
542 (set-dispatch-macro-character #\[ #\|
543 #'read-melody-bar-v3
544 *gsharp-readtable-v3*)
546 (defmethod remove-bar ((bar melody-bar))
547 (with-slots (slice) bar
548 (assert slice () 'bar-not-in-slice)
549 (with-slots (bars) slice
550 (setf bars (delete bar bars :test #'eq))
551 (unless bars
552 ;; make sure there is one bar left
553 (add-bar (make-melody-bar) slice 0)))
554 (setf slice nil)))
556 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
558 ;;; Melody layer
560 (defclass melody-layer (layer) ())
562 (defun read-melody-layer-v3 (stream char n)
563 (declare (ignore char n))
564 (apply #'make-instance 'melody-layer (read-delimited-list #\] stream t)))
566 (set-dispatch-macro-character #\[ #\_
567 #'read-melody-layer-v3
568 *gsharp-readtable-v3*)
570 (defmethod make-layer-for-staff ((staff fiveline-staff) &rest args &key staves head body tail &allow-other-keys)
571 (declare (ignore staves head body tail))
572 (apply #'make-instance 'melody-layer args))