From e30ac06ae626a2071c21602e83b50f3c4ace40df Mon Sep 17 00:00:00 2001 From: Carl Sorensen Date: Wed, 24 Nov 2010 19:42:15 -0700 Subject: [PATCH] Modify fret calculation algorithm Zero fingering creates an open string fret. Add defaultStrings property to define strings that will be used in calculating frets if no string number is specified in a chord. A bad request for an open string pitch will set a warning and set the pitch if possible on a non-open string. Add regression tests for these cases. --- input/regression/tablature-default-strings.ly | 31 +++++ input/regression/tablature-zero-finger.ly | 27 ++++ lily/fretboard-engraver.cc | 18 ++- lily/tab-note-heads-engraver.cc | 20 ++- scm/define-context-properties.scm | 3 + scm/translation-functions.scm | 123 ++++++++++++++---- 6 files changed, 190 insertions(+), 32 deletions(-) create mode 100644 input/regression/tablature-default-strings.ly create mode 100644 input/regression/tablature-zero-finger.ly diff --git a/input/regression/tablature-default-strings.ly b/input/regression/tablature-default-strings.ly new file mode 100644 index 0000000000..fb2065b374 --- /dev/null +++ b/input/regression/tablature-default-strings.ly @@ -0,0 +1,31 @@ +\version "2.13.41" + +\header { + texidoc = " +Context property @code{defaultStrings} defines desired strings +for fret calculations if no strings are defined explicitly. +" +} + +mymusic = \relative c { + 4 + \set defaultStrings = #'(5 3) + 4 + 4 + 4 + 2\6\4 + \unset defaultStrings + 2 +} + +\score { + << + \new Staff { + \clef "treble_8" + \mymusic + } + \new TabStaff { + \mymusic + } + >> +} diff --git a/input/regression/tablature-zero-finger.ly b/input/regression/tablature-zero-finger.ly new file mode 100644 index 0000000000..7c2dd4bd98 --- /dev/null +++ b/input/regression/tablature-zero-finger.ly @@ -0,0 +1,27 @@ +\version "2.13.41" + +\header { + texidoc=" +A fingering indication of zero counts as an open string for fret +calculations. An inappropriate request for an open string will generate +a warning message and set the requested pitch in the tablature. +" +} + +mymusic = \relative c { + \set minimumFret = #1 + 1 + +} + +\score { + << + \new Staff { + \clef "treble_8" + \mymusic + } + \new TabStaff { + \mymusic + } + >> +} diff --git a/lily/fretboard-engraver.cc b/lily/fretboard-engraver.cc index 4cbd08578c..c0614d7b8b 100644 --- a/lily/fretboard-engraver.cc +++ b/lily/fretboard-engraver.cc @@ -40,6 +40,7 @@ class Fretboard_engraver : public Engraver vector note_events_; vector tabstring_events_; + vector fingering_events_; public: TRANSLATOR_DECLARATIONS (Fretboard_engraver); @@ -49,6 +50,7 @@ protected: virtual void derived_mark () const; DECLARE_TRANSLATOR_LISTENER (note); DECLARE_TRANSLATOR_LISTENER (string_number); + DECLARE_TRANSLATOR_LISTENER (fingering); private: SCM last_fret_notes_; @@ -80,6 +82,13 @@ Fretboard_engraver::listen_string_number (Stream_event *ev) tabstring_events_.push_back (ev); } +IMPLEMENT_TRANSLATOR_LISTENER (Fretboard_engraver, fingering); +void +Fretboard_engraver::listen_fingering (Stream_event *ev) +{ + fingering_events_.push_back (ev); +} + void Fretboard_engraver::process_music () { @@ -89,6 +98,9 @@ Fretboard_engraver::process_music () SCM tab_strings = articulation_list (note_events_, tabstring_events_, "string-number-event"); + SCM fingers = articulation_list (note_events_, + fingering_events_, + "fingering-event"); fret_board_ = make_item ("FretBoard", note_events_[0]->self_scm ()); SCM fret_notes = ly_cxx_vector_to_list (note_events_); SCM proc = get_property ("noteToFretFunction"); @@ -96,7 +108,7 @@ Fretboard_engraver::process_music () scm_call_4 (proc, context ()->self_scm (), fret_notes, - tab_strings, + scm_list_2 (tab_strings, fingers), fret_board_->self_scm ()); SCM changes = get_property ("chordChanges"); if (to_boolean (changes) && scm_is_pair (last_fret_notes_) @@ -112,6 +124,7 @@ Fretboard_engraver::stop_translation_timestep () fret_board_ = 0; note_events_.clear (); tabstring_events_.clear (); + fingering_events_.clear (); } ADD_TRANSLATOR (Fretboard_engraver, @@ -124,6 +137,7 @@ ADD_TRANSLATOR (Fretboard_engraver, /* read */ "chordChanges " + "defaultStrings " "highStringOne " "maximumFretStretch " "minimumFret " @@ -134,5 +148,5 @@ ADD_TRANSLATOR (Fretboard_engraver, /* write */ "" - ); + ); diff --git a/lily/tab-note-heads-engraver.cc b/lily/tab-note-heads-engraver.cc index dbd6fa8517..8e81ba324e 100644 --- a/lily/tab-note-heads-engraver.cc +++ b/lily/tab-note-heads-engraver.cc @@ -43,6 +43,7 @@ class Tab_note_heads_engraver : public Engraver { vector note_events_; vector tabstring_events_; + vector fingering_events_; public: TRANSLATOR_DECLARATIONS (Tab_note_heads_engraver); @@ -50,6 +51,7 @@ public: protected: DECLARE_TRANSLATOR_LISTENER (note); DECLARE_TRANSLATOR_LISTENER (string_number); + DECLARE_TRANSLATOR_LISTENER (fingering); void process_music (); void stop_translation_timestep (); @@ -73,12 +75,22 @@ Tab_note_heads_engraver::listen_string_number (Stream_event *ev) tabstring_events_.push_back (ev); } +IMPLEMENT_TRANSLATOR_LISTENER (Tab_note_heads_engraver, fingering); +void +Tab_note_heads_engraver::listen_fingering (Stream_event *ev) +{ + fingering_events_.push_back (ev); +} + void Tab_note_heads_engraver::process_music () { SCM tab_strings = articulation_list (note_events_, tabstring_events_, "string-number-event"); + SCM defined_fingers = articulation_list (note_events_, + fingering_events_, + "fingering-event"); SCM tab_notes = ly_cxx_vector_to_list (note_events_); SCM proc = get_property ("noteToFretFunction"); SCM string_fret_finger = SCM_EOL; @@ -86,7 +98,8 @@ Tab_note_heads_engraver::process_music () string_fret_finger = scm_call_3 (proc, context ()->self_scm (), tab_notes, - tab_strings); + scm_list_2 (tab_strings, + defined_fingers)); SCM note_entry = SCM_EOL; SCM string_number = SCM_EOL; SCM fret = SCM_EOL; @@ -123,6 +136,7 @@ Tab_note_heads_engraver::stop_translation_timestep () { note_events_.clear (); tabstring_events_.clear (); + fingering_events_.clear (); } ADD_TRANSLATOR (Tab_note_heads_engraver, @@ -134,6 +148,7 @@ ADD_TRANSLATOR (Tab_note_heads_engraver, "TabNoteHead ", /* read */ + "defaultStrings " "fretLabels " "highStringOne " "middleCPosition " @@ -144,6 +159,7 @@ ADD_TRANSLATOR (Tab_note_heads_engraver, "tablatureFormat " "tabStaffLineLayoutFunction ", - /* write */ "" + /* write */ + "" ); diff --git a/scm/define-context-properties.scm b/scm/define-context-properties.scm index f758c04ca1..d4cfe13766 100644 --- a/scm/define-context-properties.scm +++ b/scm/define-context-properties.scm @@ -188,6 +188,9 @@ non-hairpin decrescendo, i.e., @samp{dim.}.") This variable is read by @rinternals{Timing_translator} at @rinternals{Score} level.") + (defaultStrings ,list? "A list of strings to use in calculating +frets for tablatures and fretboards if no strings are provided in +the notes for the current moment.") (doubleRepeatType ,string? "Set the default bar line for double repeats.") (doubleSlurs ,boolean? "If set, two slurs are created for every diff --git a/scm/translation-functions.scm b/scm/translation-functions.scm index 67653bc49a..4214d05665 100644 --- a/scm/translation-functions.scm +++ b/scm/translation-functions.scm @@ -207,9 +207,15 @@ (set! (ly:grob-property grob 'dot-placement-list) placement-list))) (define-public - (determine-frets context notes defined-strings . rest) + (determine-frets context notes specified-info . rest) "Determine string numbers and frets for playing @var{notes} -as a chord, given specified string numbers @var{defined-strings}. +as a chord, given specified information @var{specified-info}. +@var{specified-info} is a list with two list elements, +specified strings @var{defined-strings} and +specified fingerings @var{defined-fingers}. Only a fingering of +0 will affect the fret selection, as it specifies an open string. +If @var{defined-strings} is @code{'()}, the context property +@code{defaultStrings} will be used as a list of defined strings. Will look for predefined fretboards if @code{predefinedFretboardTable} is not @code {#f}. If @var{rest} is present, it contains the FretBoard grob, and a fretboard will be @@ -248,6 +254,9 @@ dot placement entries." (eq? (car l) 'open))) placement-list))) + (define (entry-count art-list) + (length (filter (lambda (x) (not (null? x))) + art-list))) (define (get-predefined-fretboard predefined-fret-table tuning pitches) "Search through @var{predefined-fret-table} looking for a predefined @@ -282,6 +291,21 @@ chords. Returns a placement-list." (string-count (length tunings)) (grob (if (null? rest) '() (car rest))) (pitches (map (lambda (x) (ly:event-property x 'pitch)) notes)) + (defined-strings (map (lambda (x) + (if (null? x) + x + (ly:event-property x 'string-number))) + (car specified-info))) + (defined-fingers (map (lambda (x) + (if (null? x) + x + (ly:event-property x 'digit))) + (cadr specified-info))) + (default-strings (ly:context-property context 'defaultStrings '())) + (strings-used (if (and (zero? (entry-count defined-strings)) + (not (zero? (entry-count default-strings)))) + default-strings + defined-strings)) (predefined-fretboard (if predefined-fret-table (get-predefined-fretboard @@ -289,12 +313,12 @@ chords. Returns a placement-list." tunings pitches) '()))) - (if (null? predefined-fretboard) (let ((string-frets (determine-frets-and-strings notes - defined-strings + strings-used + defined-fingers (ly:context-property context 'minimumFret 0) (ly:context-property context 'maximumFretStretch 4) tunings))) @@ -309,7 +333,12 @@ chords. Returns a placement-list." (define (determine-frets-and-strings - notes defined-strings minimum-fret maximum-stretch tuning) + notes + defined-strings + defined-fingers + minimum-fret + maximum-stretch + tuning) (define (calc-fret pitch string tuning) (- (ly:pitch-semitones pitch) (list-ref tuning (1- string)))) @@ -329,7 +358,8 @@ chords. Returns a placement-list." (let* ((num (ly:event-property art 'digit))) (if (and (eq? 'fingering-event (ly:event-property art 'class)) - (number? num)) + (number? num) + (> num 0)) (set! finger-found num)))) articulations) @@ -341,6 +371,11 @@ chords. Returns a placement-list." num #f))) + (define (finger-number event) + (let ((num (ly:event-property event 'digit))) + (if (number? num) + num + #f))) (define (delete-free-string string) (if (number? string) @@ -359,7 +394,8 @@ chords. Returns a placement-list." (and x y)) #t (map (lambda (specced-fret) - (> maximum-stretch (abs (- fret specced-fret)))) + (or (eq? 0 specced-fret) + (>= maximum-stretch (abs (- fret specced-fret))))) specified-frets)))) (define (string-qualifies string pitch) @@ -367,6 +403,10 @@ chords. Returns a placement-list." (and (>= fret minimum-fret) (close-enough fret)))) + (define (open-string string pitch) + (let* ((fret (calc-fret pitch string tuning))) + (eq? fret 0))) + (define string-fret-fingering-tuples '()) (define (set-fret note string) @@ -381,35 +421,62 @@ chords. Returns a placement-list." (delete-free-string string) (set! specified-frets (cons this-fret specified-frets)))) + (define (pad-list target template) + (while (< (length target) (length template)) + (begin + (set! target (if (null? target) + '(()) + (append target '(()))))) + )) + ;;; body of determine-frets-and-strings (set! free-strings (map 1+ (iota (length tuning)))) ;; get defined-strings same length as notes - (while (< (length defined-strings) (length notes)) - (set! defined-strings (append defined-strings '(())))) + (pad-list defined-strings notes) + + ;; get defined-fingers same length as notes + (pad-list defined-fingers notes) - ;; handle notes with strings assigned + ;; handle notes with strings assigned and fingering of 0 (for-each - (lambda (note string) - (if (null? string) - (set! unassigned-notes (cons note unassigned-notes)) - (let ((this-string (string-number string))) - (delete-free-string this-string) - (set-fret note this-string)))) - notes defined-strings) + (lambda (note string finger) + (let ((digit (if (null? finger) + #f + finger))) + (if (and (null? string) + (not (eq? digit 0))) + (set! unassigned-notes (cons note unassigned-notes)) + (if (eq? digit 0) + (let ((fit-string + (find (lambda (string) + (open-string string (note-pitch note))) + free-strings))) + (if fit-string + (begin + (delete-free-string fit-string) + (set-fret note fit-string)) + (begin + (ly:warning (_ "No open string for pitch ~a") + (note-pitch note)) + (set! unassigned-notes (cons note unassigned-notes))))) + (begin + (delete-free-string string) + (set-fret note string)))))) + notes defined-strings defined-fingers) ;; handle notes without strings assigned (for-each (lambda (note) - (let* ((fit-string - (find (lambda (string) - (string-qualifies string (note-pitch note))) - free-strings))) + (let ((fit-string + (find (lambda (string) + (string-qualifies string (note-pitch note))) + free-strings))) (if fit-string (set-fret note fit-string) - (ly:warning "No string for pitch ~a (given frets ~a)" - (note-pitch note) - specified-frets)))) + (ly:warning (_ "No string for pitch ~a (given frets ~a)") + (note-pitch note) + specified-frets)))) (sort unassigned-notes note-pitch>?)) string-fret-fingering-tuples) @@ -431,10 +498,10 @@ chords. Returns a placement-list." ((and (<= 0 fret-number) (< fret-number (length labels))) (list-ref labels fret-number)) (else - (ly:warning "No label for fret ~a (on string ~a); -only ~a fret labels provided" - fret-number string-number (length labels)) - "."))))) + (ly:warning (_ "No label for fret ~a (on string ~a); +only ~a fret labels provided") + fret-number string-number (length labels)) + "."))))) ;; Display the fret number as a number (define-public (fret-number-tablature-format -- 2.39.5