[frogs] chord-name-engraver plus capo

[ Thread Index | Date Index | More lilynet.net/frogs Archives ]


 Finally! Thanks Neil - it all worked!

So here it is, the new engraver. It could probably be tweaked to look a
bit nicer, but it's there and it works. And I need a column markup
function to finish it off :-) but here it is for your review.

The warning needs fixing - I haven't worked out why the compiler doesn't
like "origin".

All the changes are guarded by an "if (capo) {}", so it shouldn't change
*anything* unless the user has deliberately set capoFret in their .ly file.

The only other file modified is define-context-properties.scm, which
suppresses warnings rather than is critical. I'm away for the weekend,
so I'll put some effort into serious commenting later when I get the
chance. If you can give me any clues where I've guessed wrong it would
be appreciated. Enjoy :-)

-------------

/*
  This file is part of LilyPond, the GNU music typesetter.

  Copyright (C) 1998--2010 Jan Nieuwenhuizen <janneke@xxxxxxx>
  Copyright (C) 2010 Anthony Youngman <anthony@xxxxxxxxxxxxxxx>

  LilyPond is free software: you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation, either version 3 of the License, or
  (at your option) any later version.

  LilyPond is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with LilyPond.  If not, see <http://www.gnu.org/licenses/>.
*/

#include <iostream>

#include "chord-name.hh"
#include "context.hh"
#include "dimensions.hh"
#include "engraver.hh"        // required for all engravers ?
#include "font-interface.hh"
#include "international.hh"    // required to translate stuff eg error
messages
#include "item.hh"
#include "output-def.hh"
#include "pitch.hh"
#include "protected-scm.hh"
#include "stream-event.hh"
#include "text-interface.hh"
#include "warn.hh"

#include "translator.icc"    // required to define the engraver as a
translator

// define the class here
class Chord_name_engraver : public Engraver // declare the engraver as
an engraver
{
  TRANSLATOR_DECLARATIONS (Chord_name_engraver);
protected:

  // functions inherited from translator base class - declare and define
as required
  // virtual void initialize();        // set up for each grob as it is
created
  // void start_translation_timestep();    // this is called for all
active grobs at the start of each timeslice ?
  void process_music ();        // called after all events have been heard
  // void process_acknowledged();    //    or grobs acknowledged
  void stop_translation_timestep ();    // called for active grobs at
the end of each timeslice
  virtual void finalize ();        // tie up any loose ends when the
grob finishes

  // other functions
  virtual void derived_mark () const;
  DECLARE_TRANSLATOR_LISTENER (note);    // tells the engraver to
process notes ?
  DECLARE_TRANSLATOR_LISTENER (rest);    // tells the engraver to
process rests ?
 
private:    // member variable names end with an underscore
  Item *chord_name_;
  vector<Stream_event*> notes_;
 
  SCM last_chord_;
  Stream_event *rest_event_;
 
  // ugh - surely there's a better way to do this ...
  Pitch capo_transpose_[11];    // store the pitches to transpose down by
};

Chord_name_engraver::Chord_name_engraver ()
// As each context is created, the engravers are attached. This
initialises the engraver.
// these variables carry context forward from note to note.
{
  chord_name_ = 0;
  last_chord_ = SCM_EOL;
  rest_event_ = 0;
 
  // assume we're starting from C and transposing down // see above -
this must be a crap way of doing it
  capo_transpose_[0] = Pitch (0, -1, 0);    // B
  capo_transpose_[1] = Pitch (0, -1, FLAT_ALTERATION);    // B flat
  capo_transpose_[2] = Pitch (0, -2, 0);    // A
  capo_transpose_[3] = Pitch (0, -2, FLAT_ALTERATION);    // A flat
  capo_transpose_[4] = Pitch (0, -3, 0);    // G
  capo_transpose_[5] = Pitch (0, -4, SHARP_ALTERATION);    // F sharp
  capo_transpose_[6] = Pitch (0, -4, 0);    // F
  capo_transpose_[7] = Pitch (0, -5, 0);    // E
  capo_transpose_[8] = Pitch (0, -5, FLAT_ALTERATION);    // E flat
  capo_transpose_[9] = Pitch (0, -6, 0);    // D
  capo_transpose_[10] = Pitch (0, -7, SHARP_ALTERATION);    // C sharp
}

// initialize()

// start_translation_timestep()

void
Chord_name_engraver::process_music ()
// This is called for each note or rest.
// It is called as a result of being registered with
IMPLEMENT_TRANSLATOR_LISTENER
{
  SCM markup;        // to store markup in
  SCM bass = SCM_EOL;    // remember notes to create chord
  SCM inversion = SCM_EOL;
  SCM pitches = SCM_EOL;

  SCM capo_markup;    // and transposed notes for capo chord
  SCM capo_bass = SCM_EOL;
  SCM capo_inversion = SCM_EOL;
  SCM capo_pitches = SCM_EOL;

  int capo = 0;        // declare capo as both flag and value
 
  if (rest_event_)
    {    // process a rest
      SCM no_chord_markup = get_property ("noChordSymbol");
      if (!Text_interface::is_markup (no_chord_markup))
        return;
      markup = no_chord_markup;
    }
  else
    {  // process a note event
      if (!notes_.size ())
        return;

      // get capo fret
      // This is set by "\set ChordNames.capoFret = #3" in the lily
source file
      // declare properties in define-context-properties.scm
      SCM capofret = get_property ("capoFret");
      if (scm_is_number (capofret))
      {
    capo = scm_to_int (capofret);
    if (capo < 0 || capo > 23)
    {
// !!! TODO !!! FIX !!!
//      origin ()->warning (_f ("invalid capo fret; must be 0 to 23,
found: \"%i\"",
//                  capo));
      capo = 0;    // set to 0 to ignore
    }
    if (capo >= 12)
      capo -= 12;    // if you can put the capo above 12th fret, we
don't care about octaves
      }
     
      Stream_event *inversion_event = 0;
      for (vsize i = 0; i < notes_.size (); i++)
      {    // step through each note in turn
        Stream_event *n = notes_[i];
        SCM p = n->get_property ("pitch");
        if (!unsmob_pitch (p))
          continue;

        if (n->get_property ("inversion") == SCM_BOOL_T)
        {
          inversion_event = n;
          inversion = p;
      if (capo)
      { // transpose the pitch // note that p here is not p above!
        Pitch *p = unsmob_pitch (n->get_property ("pitch"));    //
convert the pitch to a C++ class (Pitch)
        Pitch orig = p->transposed (capo_transpose_[capo-1]);    // Now
transpose it
        capo_inversion = orig.smobbed_copy ();            // and convert
it back to a scheme object
      }
    }
        else if (n->get_property ("bass") == SCM_BOOL_T)
    {
          bass = p;
      if (capo) {
        Pitch *p = unsmob_pitch (n->get_property ("pitch"));
        Pitch orig = p->transposed (capo_transpose_[capo-1]);
        capo_bass = orig.smobbed_copy ();
      }
    }
        else
    {
          pitches = scm_cons (p, pitches);
      if (capo) {
        Pitch *p = unsmob_pitch (n->get_property ("pitch"));
        Pitch orig = p->transposed (capo_transpose_[capo-1]);
        capo_pitches = scm_cons ( orig.smobbed_copy (), capo_pitches);
      }
    }
      }

      if (inversion_event)
      {
        SCM oct = inversion_event->get_property ("octavation");
        if (scm_is_number (oct))
        {
          Pitch *p = unsmob_pitch (inversion_event->get_property ("pitch"));
          int octavation = scm_to_int (oct);
          Pitch orig = p->transposed (Pitch (-octavation, 0, 0));

          pitches = scm_cons (orig.smobbed_copy (), pitches);
     
      // if capo sort this out later !!!
     
        }
        else
          programming_error ("inversion does not have original pitch");
      }

      pitches = scm_sort_list (pitches, Pitch::less_p_proc);
      if (capo)
    capo_pitches = scm_sort_list (capo_pitches, Pitch::less_p_proc);
   
      SCM name_proc = get_property ("chordNameFunction");
      markup = scm_call_4 (name_proc, pitches, bass, inversion,
          context ()->self_scm ());
      if (capo)
    capo_markup = scm_call_4 (name_proc, capo_pitches, capo_bass,
capo_inversion,
        context ()->self_scm ());
    }
  /*
    Ugh. // Why's this ughly?
  */
 
  // do we need any more capo stuff here beyond setting the chord name text?
 
  SCM chord_as_scm = scm_cons (pitches, scm_cons (bass, inversion));

  chord_name_ = make_item ("ChordName",
      rest_event_ ? rest_event_->self_scm () : notes_[0]->self_scm ());
  if (!capo)
    chord_name_->set_property ("text", markup);
  else
  {
    SCM capovertical = get_property ("capoVertical");
    // how do I combine text and markup to get "markup (capo_markup)" !!!
    SCM paren_proc = ly_lily_module_constant ("parenthesize-markup");
    SCM line_proc = ly_lily_module_constant ("line-markup");
    SCM hspace_proc = ly_lily_module_constant ("hspace-markup");

  SCM final_markup = scm_list_n (line_proc,
                 scm_list_3 (markup,
                         scm_list_2 (hspace_proc,
                             scm_from_int (1)),
                         scm_list_2 (paren_proc, capo_markup)),
                 SCM_UNDEFINED);

    chord_name_->set_property ("text", final_markup);
  }

  SCM chord_changes = get_property("chordChanges");
  if (to_boolean (chord_changes) && scm_is_pair (last_chord_)
      && ly_is_equal (chord_as_scm, last_chord_))
    chord_name_->set_property ("begin-of-line-visible", SCM_BOOL_T);

  last_chord_ = chord_as_scm;
}

// process_acknowledged()

void
Chord_name_engraver::stop_translation_timestep ()
{
  chord_name_ = 0;
  notes_.clear ();
  rest_event_ = 0;
}

void
Chord_name_engraver::finalize ()
{
}

void
Chord_name_engraver::derived_mark () const
{
  scm_gc_mark (last_chord_);
}



IMPLEMENT_TRANSLATOR_LISTENER (Chord_name_engraver, note);
void
Chord_name_engraver::listen_note (Stream_event *ev)
{
  notes_.push_back (ev);
}

IMPLEMENT_TRANSLATOR_LISTENER (Chord_name_engraver, rest);
void
Chord_name_engraver::listen_rest (Stream_event *ev)
{
  ASSIGN_EVENT_ONCE(rest_event_, ev);
}
 

/*
  The READs description is not strictly accurate:
  which properties are read depend on the chord naming function active.
*/
ADD_TRANSLATOR (Chord_name_engraver,
        /* doc */
        "Catch note and rest events and generate the appropriate
chordname.",

        /* create */
        "ChordName ",

        /* read */    // list of properties it reads
        "capoFret "    // added for capo
        "capoVertical "
        "chordChanges "
        "chordNameExceptions "
        "chordNameFunction "
        "chordNoteNamer "
        "chordRootNamer "
        "chordNameExceptions "
        "majorSevenSymbol "
                "noChordSymbol ",

        /* write */
        ""
        );


---
----
Join the Frogs!


Mail converted by MHonArc 2.6.19+ http://listengine.tuxfamily.org/