1 (in-package :gsharp-buffer
)
3 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
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
(staffwise-element 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
))
29 ((:treble
:treble8
) 2)
33 (make-instance 'clef
:name name
:lineno lineno
))
35 (defmethod slots-to-be-saved append
((c clef
))
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
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
))
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
))
60 ((:treble
:treble8
) (+ (lineno clef
) 6))
61 (:c
(+ (lineno clef
) 3))))
63 (defmethod bottom-line ((clef clef
))
71 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
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
)
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
))
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 (defgeneric set-contents
(element contents
)
115 (:documentation
"Sets note in an element"))
119 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
123 ;;; Notes are immutable objets. If you want to alter (say) the staff
124 ;;; or the pitch of a note, you have to delete it and add a new note
125 ;;; with the right characteristics.
127 ;;; Return the pitch of the note.
128 (defgeneric pitch
(note))
130 ;;; Return the accidentals of the note. The value returned is one of
131 ;;; :natural :flat :double-flat :sharp or :double-sharp.
132 (defgeneric accidentals
(note))
134 ;;; Return a non-negative integer indicating the number of dots of the
135 ;;; note. The value nil is returned whenever the note takes its
136 ;;; number of dots from the cluster to which it belongs.
137 (defgeneric dots
(note))
139 ;;; Returns the cluster to which the note belongs, or nil if the note
140 ;;; currently does not belong to any cluster.
141 (defgeneric cluster
(note))
143 ;;; The pitch is a number from 0 to 128
145 ;;; The staff is a staff object.
147 ;;; Head can be :long, :breve, :whole, :half, :filled, or nil. A
148 ;;; value of nil means that the notehead is determined by that of the
149 ;;; cluster to which the note belongs.
151 ;;; Accidentals can be :natural :flat :double-flat :sharp or :double-sharp.
152 ;;; The default is :natural. Whether a note is actually displayed
153 ;;; preceded by one of the corresponding signs is a matter of context and
156 ;;; The number of dots can be an integer or nil, meaning that the number
157 ;;; of dots is taken from the cluster. The default value is nil.
159 ;;; The actual duration of the note is computed from the note head, the
160 ;;; number of beams of the cluster to which the note belongs, and the
161 ;;; number of dots in the usual way.
163 (defclass note
(gsharp-object)
164 ((cluster :initform nil
:initarg
:cluster
:accessor cluster
)
165 (pitch :initarg
:pitch
:reader pitch
:type
(integer 0 127))
166 (staff :initarg
:staff
:reader staff
:type staff
)
167 (head :initform nil
:initarg
:head
:reader head
168 :type
(or (member :long
:breve
:whole
:half
:filled
) null
))
169 (accidentals :initform
:natural
:initarg
:accidentals
:reader accidentals
170 ;; FIXME: we want :TYPE ACCIDENTAL here but need to
171 ;; sort out order of definition for that to be useful.
173 :type
(member :natural
:flat
:double-flat
:sharp
:double-sharp
))
174 (dots :initform nil
:initarg
:dots
:reader dots
175 :type
(or (integer 0 3) null
))
176 (%tie-right
:initform nil
:initarg
:tie-right
:accessor tie-right
)
177 (%tie-left
:initform nil
:initarg
:tie-left
:accessor tie-left
)))
179 (defun make-note (pitch staff
&rest args
&key head
(accidentals :natural
) dots
)
180 (declare (type (integer 0 127) pitch
)
182 (type (or (member :long
:breve
:whole
:half
:filled
) null
) head
)
183 ;; FIXME: :TYPE ACCIDENTAL
185 (type (member :natural
:flat
:double-flat
:sharp
:double-sharp
)
187 (type (or (integer 0 3) null
) dots
)
188 (ignore head accidentals dots
))
189 (apply #'make-instance
'note
:pitch pitch
:staff staff args
))
191 (defmethod slots-to-be-saved append
((n note
))
192 '(pitch staff head accidentals dots %tie-right %tie-left
))
194 (defun read-note-v3 (stream char n
)
195 (declare (ignore char n
))
196 (apply #'make-instance
'note
(read-delimited-list #\
] stream t
)))
198 (set-dispatch-macro-character #\
[ #\N
200 *gsharp-readtable-v3
*)
202 ;;; Return true if note1 is considered less than note2.
203 (defun note-less (note1 note2
)
204 (< (pitch note1
) (pitch note2
)))
206 ;;; Return true if note1 is considered equal to note2.
207 (defun note-equal (note1 note2
)
208 (= (pitch note1
) (pitch note2
)))
211 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
213 ;;; Tuning (support for microtonal and historical tunings/temperaments)
215 ;;; FIXME: add name-mixin also?
216 (defclass tuning
(gsharp-object)
217 ((master-pitch-note :initform
(make-instance 'note
:pitch
33 ; a above middle c
218 :staff
(make-instance 'staff
))
219 :initarg
:master-pitch-note
221 :accessor master-pitch-note
)
222 (master-pitch-freq :initform
440
223 :initarg
:master-pitch-freq
224 :accessor master-pitch-freq
)))
226 (defmethod slots-to-be-saved append
((tuning tuning
))
227 '(master-pitch-note master-pitch-freq
))
229 ;;; Returns how a note should be tuned in a given tuning
230 ;;; in terms of a cent value.
231 (defgeneric note-cents
(note tuning
))
233 ;;; 12-edo is provided for efficiency only. It is a
234 ;;; special case of a regular temperament. Perhaps it
235 ;;; should be removed?
236 (defclass 12-edo
(tuning)
239 (defmethod slots-to-be-saved append
((tuning 12-edo
))
242 (defmethod note-cents ((note note
) (tuning 12-edo
))
243 (multiple-value-bind (octave pitch
) (floor (pitch note
) 7)
244 (+ (* 1200 (1+ octave
))
245 (ecase pitch
(0 0) (1 200) (2 400) (3 500) (4 700) (5 900) (6 1100))
246 (ecase (accidentals note
)
255 (:double-sharp
200)))))
257 ;;; regular temperaments are temperaments that
258 ;;; retain their interval sizes regardless of modulation, as opposed to
259 ;;; irregular temperaments.
260 (defclass regular-temperament
(tuning)
261 ((octave-cents :initform
1200 :initarg
:octave-cents
:accessor octave-cents
)
262 (fifth-cents :initform
700 :initarg
:fifth-cents
:accessor fifth-cents
)
263 (quartertone-cents :initform
50 :initarg
:quartertone-cents
:accessor quartertone-cents
)
264 ;; TODO: Add cent sizes of various microtonal accidentals, perhaps in an alist?
267 (defmethod slots-to-be-saved append
((tuning regular-temperament
))
268 '(octave-cents fifth-cents
))
270 (defmethod note-cents ((note note
) (tuning regular-temperament
))
273 (sharps 0) ;; short for 7 fifths up and 4 octaves down
275 (incf octaves
(floor (pitch note
) 7))
276 (ecase (mod (pitch note
) 7)
278 (1 (progn (incf octaves -
1) (incf fifths
2)))
279 (2 (progn (incf octaves -
2) (incf fifths
4)))
280 (3 (progn (incf octaves
1) (incf fifths -
1)))
281 (4 (progn (incf fifths
1)))
282 (5 (progn (incf octaves -
1) (incf fifths
3)))
283 (6 (progn (incf octaves -
2) (incf fifths
5))))
284 (ecase (accidentals note
)
285 (:double-flat
(incf sharps -
2))
286 (:sesquiflat
(incf sharps -
1) (incf quartertones -
1))
287 (:flat
(incf sharps -
1))
288 (:semiflat
(incf quartertones -
1))
290 (:semisharp
(incf quartertones
1))
291 (:sharp
(incf sharps
1))
292 (:sesquisharp
(incf sharps
1) (incf quartertones
1))
293 (:double-sharp
(incf sharps
2)))
294 (incf octaves
(* -
4 sharps
))
295 (incf fifths
(* 7 sharps
))
296 (+ (* octaves
(octave-cents tuning
))
297 (* fifths
(fifth-cents tuning
))
298 (* quartertones
(quartertone-cents tuning
)))))
300 ;;; TODO: (defclass irregular-temperament ...)
302 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
306 (defclass melody-element
(rhythmic-element) ())
308 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
312 (defgeneric alterations
(key-signature)
313 (:documentation
"return the alterations in the form of a
314 7-element array where each element is either :natural,
315 :sharp, or :flat according to how each staff position
318 (defgeneric more-sharps
(key-signature &optional n
)
319 (:documentation
"make the key signature N alterations
320 sharper by removing some flats and/or adding some sharps"))
322 (defgeneric more-flats
(key-signature &optional n
)
323 (:documentation
"make the key signature N alterations
324 flatter by removing some sharps and/or adding some flats"))
326 (defclass staffwise-element
(element)
327 ((%staff
:initarg
:staff
:reader staff
)))
328 (defmethod slots-to-be-saved append
((s-e staffwise-element
))
331 (defclass key-signature
(staffwise-element)
332 ((%alterations
:initform
(make-array 7 :initial-element
:natural
)
333 :initarg
:alterations
:reader alterations
)))
335 (defun make-key-signature (staff &rest args
&key alterations
)
336 (declare (type (or null
(simple-vector 7)) alterations
)
337 (ignore alterations
))
338 (apply #'make-instance
'key-signature
:staff staff args
))
340 (defmethod slots-to-be-saved append
((k key-signature
))
343 (defmethod more-sharps ((sig key-signature
) &optional
(n 1))
344 (let ((alt (alterations sig
)))
346 do
(cond ((eq (aref alt
3) :flat
) (setf (aref alt
3) :natural
))
347 ((eq (aref alt
0) :flat
) (setf (aref alt
0) :natural
))
348 ((eq (aref alt
4) :flat
) (setf (aref alt
4) :natural
))
349 ((eq (aref alt
1) :flat
) (setf (aref alt
1) :natural
))
350 ((eq (aref alt
5) :flat
) (setf (aref alt
5) :natural
))
351 ((eq (aref alt
2) :flat
) (setf (aref alt
2) :natural
))
352 ((eq (aref alt
6) :flat
) (setf (aref alt
6) :natural
))
353 ((eq (aref alt
3) :natural
) (setf (aref alt
3) :sharp
))
354 ((eq (aref alt
0) :natural
) (setf (aref alt
0) :sharp
))
355 ((eq (aref alt
4) :natural
) (setf (aref alt
4) :sharp
))
356 ((eq (aref alt
1) :natural
) (setf (aref alt
1) :sharp
))
357 ((eq (aref alt
5) :natural
) (setf (aref alt
5) :sharp
))
358 ((eq (aref alt
2) :natural
) (setf (aref alt
2) :sharp
))
359 ((eq (aref alt
6) :natural
) (setf (aref alt
6) :sharp
))))))
361 (defmethod more-flats ((sig key-signature
) &optional
(n 1))
362 (let ((alt (alterations sig
)))
364 do
(cond ((eq (aref alt
6) :sharp
) (setf (aref alt
6) :natural
))
365 ((eq (aref alt
2) :sharp
) (setf (aref alt
2) :natural
))
366 ((eq (aref alt
5) :sharp
) (setf (aref alt
5) :natural
))
367 ((eq (aref alt
1) :sharp
) (setf (aref alt
1) :natural
))
368 ((eq (aref alt
4) :sharp
) (setf (aref alt
4) :natural
))
369 ((eq (aref alt
0) :sharp
) (setf (aref alt
0) :natural
))
370 ((eq (aref alt
3) :sharp
) (setf (aref alt
3) :natural
))
371 ((eq (aref alt
6) :natural
) (setf (aref alt
6) :flat
))
372 ((eq (aref alt
2) :natural
) (setf (aref alt
2) :flat
))
373 ((eq (aref alt
5) :natural
) (setf (aref alt
5) :flat
))
374 ((eq (aref alt
1) :natural
) (setf (aref alt
1) :flat
))
375 ((eq (aref alt
4) :natural
) (setf (aref alt
4) :flat
))
376 ((eq (aref alt
0) :natural
) (setf (aref alt
0) :flat
))
377 ((eq (aref alt
3) :natural
) (setf (aref alt
3) :flat
))))))
379 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
382 ;; * no make function (no type checking)
383 ;; * slots-to-be-saved only 'cos it's there
384 ;; * What accessors do we need (if any)?
385 ;; * Should I copy the (keysig) functionality from gui.lisp?
387 (defclass time-signature
(staffwise-element)
388 ((%components
:initarg
:components
:reader time-signature-components
390 (defmethod slots-to-be-saved append
((t-s time-signature
))
392 (defun make-time-signature (staff &rest args
)
393 (apply #'make-instance
'time-signature
:staff staff args
))
395 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
399 ;;; Return a list of the notes of the cluster
400 (defgeneric notes
(cluster))
402 ;;; Add a note to the cluster. It is an error if there is already a
403 ;;; note in the cluster with the same staff and the same pitch.
404 (defgeneric add-note
(cluster note
))
406 ;;; Find a note in a cluster. The comparison is made using only the
407 ;;; pitch of the supplied note. If the note does not exist nil is returned.
408 (defgeneric find-note
(cluster note
))
410 ;;; Delete a note from the cluster to which it belongs. It is an
411 ;;; error to call this function if the note currently does not belong
413 (defgeneric remove-note
(note))
415 (defclass cluster
(melody-element)
416 ((notes :initform
'() :initarg
:notes
:accessor notes
)
417 (stem-direction :initform
:auto
:initarg
:stem-direction
:accessor stem-direction
)))
419 (defmethod initialize-instance :after
((c cluster
) &rest args
)
420 (declare (ignore args
))
421 (loop for note in
(notes c
)
422 do
(setf (cluster note
) c
)))
424 (defun make-cluster (&rest args
425 &key
(notehead :filled
) (lbeams 0) (rbeams 0) (dots 0)
426 (xoffset 0) notes
(stem-direction :auto
))
427 (declare (type (member :long
:breve
:whole
:half
:filled
) notehead
)
428 (type (integer 0 5) lbeams
)
429 (type (integer 0 5) rbeams
)
430 (type (integer 0 3) dots
)
431 (type number xoffset
)
433 (type (member :up
:down
:auto
) stem-direction
)
434 (ignore notehead lbeams rbeams dots xoffset notes stem-direction
))
435 (apply #'make-instance
'cluster args
))
437 (defmethod slots-to-be-saved append
((c cluster
))
438 '(stem-direction notes
))
440 (defun read-cluster-v3 (stream char n
)
441 (declare (ignore char n
))
442 (apply #'make-instance
'cluster
(read-delimited-list #\
] stream t
)))
444 (set-dispatch-macro-character #\
[ #\%
446 *gsharp-readtable-v3
*)
448 (define-condition note-already-in-cluster
(gsharp-condition) ()
450 (lambda (condition stream
)
451 (declare (ignore condition
))
452 (format stream
"Attempt to add a note already in a cluster"))))
454 (defmethod set-contents ((cluster cluster
) contents
)
455 (setf (notes cluster
) contents
))
457 (defmethod add-note ((cluster cluster
) (note note
))
458 (with-slots (notes) cluster
459 (assert (not (find note notes
:test
#'note-equal
))
461 'note-already-in-cluster
)
462 (set-contents cluster
(merge 'list notes
(list note
) #'note-less
))
463 (setf (cluster note
) cluster
)))
465 (defmethod find-note ((cluster cluster
) (note note
))
466 (with-slots (notes) cluster
467 (car (member (pitch note
) notes
:key
#'pitch
))))
469 (define-condition note-not-in-cluster
(gsharp-condition) ()
471 (lambda (condition stream
)
472 (declare (ignore condition
))
473 (format stream
"Attempt to delete a note not in a cluster"))))
475 (defmethod remove-note ((note note
))
476 (with-slots (cluster) note
477 (assert cluster
() 'note-not-in-cluster
)
478 (set-contents cluster
(delete note
(notes cluster
) :test
#'eq
))))
480 (defun lower-bound (bound list
&key
(test #'<))
481 "Return the `largest' element in the sorted list LIST such that
482 \(TEST element BOUND) is true."
485 (unless (funcall test item bound
)
486 (return-from lower-bound last
))
490 (defmethod cluster-lower-bound ((cluster cluster
) (bound note
))
491 (with-slots (notes) cluster
492 (lower-bound bound notes
:test
#'note-less
)))
494 (defmethod cluster-upper-bound ((cluster cluster
) (bound note
))
495 (with-slots (notes) cluster
496 (lower-bound bound
(reverse notes
) :test
(complement #'note-less
))))
498 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
502 (defclass rest
(melody-element)
503 ((staff :initarg
:staff
:reader staff
)
504 (staff-pos :initarg
:staff-pos
:initform
4 :reader staff-pos
)))
506 (defun make-rest (staff &rest args
507 &key
(staff-pos 4) (notehead :filled
) (lbeams 0) (rbeams 0)
508 (dots 0) (xoffset 0))
509 (declare (type staff staff
)
510 (type integer staff-pos
)
511 (type (member :long
:breve
:whole
:half
:filled
) notehead
)
512 (type (integer 0 5) lbeams
)
513 (type (integer 0 5) rbeams
)
514 (type (integer 0 3) dots
)
515 (type number xoffset
)
516 (ignore staff-pos notehead lbeams rbeams dots xoffset
))
517 (apply #'make-instance
'rest
520 (defmethod slots-to-be-saved append
((s rest
))
523 (defun read-rest-v3 (stream char n
)
524 (declare (ignore char n
))
525 (apply #'make-instance
'rest
(read-delimited-list #\
] stream t
)))
527 (set-dispatch-macro-character #\
[ #\-
529 *gsharp-readtable-v3
*)
531 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
535 (defclass melody-bar
(bar) ())
537 (defun make-melody-bar (&rest args
&key elements
)
538 (declare (type list elements
)
540 (apply #'make-instance
'melody-bar args
))
542 (defmethod make-bar-for-staff ((staff fiveline-staff
) &rest args
&key elements
)
543 (declare (ignore elements
))
544 (apply #'make-instance
'melody-bar args
))
546 (defun read-melody-bar-v3 (stream char n
)
547 (declare (ignore char n
))
548 (apply #'make-instance
'melody-bar
(read-delimited-list #\
] stream t
)))
550 (set-dispatch-macro-character #\
[ #\|
552 *gsharp-readtable-v3
*)
554 (defmethod remove-bar ((bar melody-bar
))
555 (with-slots (slice) bar
556 (assert slice
() 'bar-not-in-slice
)
557 (with-slots (bars) slice
558 (setf bars
(delete bar bars
:test
#'eq
))
560 ;; make sure there is one bar left
561 (add-bar (make-melody-bar) slice
0)))
564 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
568 (defclass melody-layer
(layer) ())
570 (defun read-melody-layer-v3 (stream char n
)
571 (declare (ignore char n
))
572 (apply #'make-instance
'melody-layer
(read-delimited-list #\
] stream t
)))
574 (set-dispatch-macro-character #\
[ #\_
575 #'read-melody-layer-v3
576 *gsharp-readtable-v3
*)
578 (defmethod make-layer-for-staff ((staff fiveline-staff
) &rest args
&key staves head body tail
&allow-other-keys
)
579 (declare (ignore staves head body tail
))
580 (apply #'make-instance
'melody-layer args
))
582 (defgeneric clefs
(staff)
583 (:method
((s t
)) nil
)
584 (:method
((s fiveline-staff
))
585 (remove-if #'(lambda (x) (not (typep x
'clef
)))
586 (staffwise-elements s
))))