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)))) |