-;; make-engraver helper macro
-
-(defmacro-public make-engraver forms
- "Helper macro for creating Scheme engravers.
-
-The usual form for an engraver is an association list (or alist)
-mapping symbols to either anonymous functions or to another such
-alist.
-
-@code{make-engraver} accepts forms where the first element is either
-an argument list starting with the respective symbol, followed by the
-function body (comparable to the way @code{define} is used for
-defining functions), or a single symbol followed by subordinate forms
-in the same manner. You can also just make an alist pair
-literally (the @samp{car} is quoted automatically) as long as the
-unevaluated @samp{cdr} is not a pair. This is useful if you already
-have defined your engraver functions separately.
-
-Symbols mapping to a function would be @code{initialize},
-@code{start-translation-timestep}, @code{process-music},
-@code{process-acknowledged}, @code{stop-translation-timestep}, and
-@code{finalize}. Symbols mapping to another alist specified in the
-same manner are @code{listeners} with the subordinate symbols being
-event classes, and @code{acknowledgers} and @code{end-acknowledgers}
-with the subordinate symbols being interfaces."
- (let loop ((forms forms))
- (if (cheap-list? forms)
- `(list
- ,@(map (lambda (form)
- (if (pair? (car form))
- `(cons ',(caar form) (lambda ,(cdar form) ,@(cdr form)))
- `(cons ',(car form) ,(loop (cdr form)))))
- forms))
- forms)))
+;; pitch recognition
+
+(define-public (make-semitone->pitch pitches)
+ "Convert @var{pitches}, an unordered list of note values
+covering (after disregarding octaves) all absolute pitches in need of
+conversion, into a function converting semitone numbers (absolute
+pitch missing enharmonic information) back into note values.
+
+For a key signature without accidentals
+@example
+c cis d es e f fis g gis a bes b
+@end example
+might be a good choice, covering Bb major to A major and their
+parallel keys, and melodic/harmonic C minor to A minor."
+ ;; TODO: short-circuit lcm calculation once we know it will be large
+ (let* ((size (apply lcm (map (lambda (pitch)
+ (denominator (/ (ly:pitch-tones pitch) 6)))
+ pitches)))
+ ;; Normal tunings need 12 steps per octave, quartertone
+ ;; tunings 24, Makam needs 108. But microtunings might cause
+ ;; trouble.
+ (lookup (if (> size 400)
+ (make-hash-table)
+ (make-vector size #f))))
+ (for-each
+ (lambda (pitch)
+ (let* ((rawoct (/ (ly:pitch-tones pitch) 6))
+ (oct (floor rawoct))
+ (ref (- rawoct oct))
+ (val (ly:pitch-transpose pitch
+ (ly:make-pitch (- oct) 0))))
+ (if (hash-table? lookup)
+ (hashv-set! lookup ref val)
+ (vector-set! lookup (* size ref) val))))
+ pitches)
+ (lambda (semitone)
+ "Convert @var{semitone} numbers into note values. If the
+originally specified list of pitches does not contain a note
+corresponding to @var{semitone} (disregarding octaves), @code{#f} is
+returned."
+ (let* ((rawoct (/ semitone 12))
+ (oct (floor rawoct))
+ (ref (- rawoct oct))
+ (val (if (hash-table? lookup)
+ (hashv-ref lookup ref)
+ (let ((ref (* (vector-length lookup) ref)))
+ (and (integer? ref)
+ (vector-ref lookup ref))))))
+ (and val
+ (ly:pitch-transpose val (ly:make-pitch oct 0)))))))
+
+(define ((shift-semitone->pitch key semitone->pitch) semitone)
+ "Given a function @var{semitone->pitch} converting a semitone number
+into a note value for a lookup table created in relation to@tie{}C,
+returns a corresponding function in relation to @var{key}. The note
+values returned by this function differ only enharmonically from the
+original @var{semitone->pitch} function."
+ (ly:pitch-transpose (semitone->pitch (- semitone (* 2 (ly:pitch-tones key))))
+ key))
+
+(export shift-semitone->pitch)