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
(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 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
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
140 ;;; The staff is a staff object.
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.
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
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.
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.
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
)
177 (type (or (member :long
:breve
:whole
:half
:filled
) null
) head
)
178 ;; FIXME: :TYPE ACCIDENTAL
180 (type (member :natural
:flat
:double-flat
:sharp
:double-sharp
)
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
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
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
))
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
)
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
))
268 (sharps 0) ;; short for 7 fifths up and 4 octaves down
270 (incf octaves
(floor (pitch note
) 7))
271 (ecase (mod (pitch note
) 7)
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))
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 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
301 (defclass melody-element
(rhythmic-element) ())
303 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
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
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
))
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
))
338 (defmethod more-sharps ((sig key-signature
) &optional
(n 1))
339 (let ((alt (alterations sig
)))
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
)))
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 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
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
385 (defmethod slots-to-be-saved append
((t-s time-signature
))
388 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
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
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
)
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 #\
[ #\%
439 *gsharp-readtable-v3
*)
441 (define-condition note-already-in-cluster
(gsharp-condition) ()
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) ()
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
)))
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."
477 (unless (funcall test item bound
)
478 (return-from lower-bound 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 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
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
512 (defmethod slots-to-be-saved append
((s rest
))
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 #\
[ #\-
521 *gsharp-readtable-v3
*)
523 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
527 (defclass melody-bar
(bar) ())
529 (defun make-melody-bar (&rest args
&key elements
)
530 (declare (type list 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 #\
[ #\|
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
))
552 ;; make sure there is one bar left
553 (add-bar (make-melody-bar) slice
0)))
556 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
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
))