User Tools

Site Tools


software:repositiontext

Reposition Text for musescore

The code below repositions any (fingering) text which is between “0” and “5” above or below a note, depending on the current rough position: If the current text is above the staff, it will be positioned above the note. If the current text is below the staff, it will be positioned below the note.

If text should not be moved, add a space after the number, like: “0 ”

//=============================================================================
//  MuseScore
//  Music Composition & Notation
//
//  Copyright (C) 2012 Werner Schweer
//  Copyright (C) 2013, 2014 Nicolas Froment, Joachim Schmitz
//  Copyright (C) 2014 Jörn Eichler
//  Copyright (C) 2019 Marc Nijdam
//
//  This program is free software; you can redistribute it and/or modify
//  it under the terms of the GNU General Public License version 2
//  as published by the Free Software Foundation and appearing in
//  the file LICENCE.GPL
//
// console.log(Object.getOwnPropertyNames(segment.annotations[i].parent).sort());
//=============================================================================
 
import QtQuick 2.0
import MuseScore 1.0
 
MuseScore {
  version:  "1.0"
  description: "This plugin moves (staff and note) fingering text below or above a note in a chord and avoid the staff as well."
  menuPath: "Plugins.Notes.Reposition Text"
 
  // Apply the given function to all notes in selection
  // or, if nothing is selected, in the entire score
 
  function applyToNotesInSelection() {
    console.log("===============================================");
    var cursor = curScore.newCursor();
    cursor.rewind(1);
    var Y_MID_STAFF = 2 // mid between staff text vertical offset top: 0.00, bottom: 4.00
    var DMAX = 100 // largest horizontal distance between note and text.
    var DMIN = 1.5 // "magnetic range" in which a note can pull a finger towards itself
    var startStaff;
    var endStaff;
    var endTick;
    var fullScore = false;
    if (!cursor.segment) { // no selection
      fullScore = true;
      startStaff = 0; // start with 1st staff
      endStaff = curScore.nstaves - 1; // and end with last
    } else {
      startStaff = cursor.staffIdx;
      cursor.rewind(2);
      if (cursor.tick == 0) {
        // this happens when the selection includes
        // the last measure of the score.
        // rewind(2) goes behind the last segment (where
        // there's none) and sets tick=0
        endTick = curScore.lastSegment.tick + 1;
      } else {
        endTick = cursor.tick;
      }
      endStaff = cursor.staffIdx;
    }
    console.log(startStaff + " - " + endStaff + " - " + endTick)
 
    // Fingering structure:
    //   id_st: object index for staff annotation
    //   id_el: element object index for note object
    //   id_nt: parent (note) object index for text object
    //   id_co: parent (note) object index for text position
    //   id_ne: text object index for note_element
    //     if any of the id_.. == -1, it is not in use
    //   staff: for staff annotation, staff to which text belongs
    //   x_pos: x-position of text (to find relative position between texts)
    //   y_pos: y-position of text (to find relative position between texts)
    var Fingering = new Array(); // object with fingering text
 
    // *********************************************
    // *
    // *                      1
    // *
    // *********************************************
    for (var staff = startStaff; staff <= endStaff; staff++) {
      cursor.rewind(1); // sets voice to 0
      cursor.voice = 0; //voice has to be set after goTo
      cursor.staffIdx = staff;
 
      if (fullScore)
        cursor.rewind(0) // if no selection, beginning of score
 
      var topFromStaff = -1
      var bottomFromStaff = 5
      var distToNote = 1.5
      var distToBeam = 1
 
    // *********************************************
    // *
    // *                      2
    // *
    // *********************************************
 
      var segment = cursor.segment
 
      while (segment && (fullScore || segment.tick < endTick)) {
        var ya = topFromStaff // fingering above
        var yb = bottomFromStaff // fingering below
        // Search for Staff (fingering) text (which is not attached to a note)
        // for matching staff and fingering text with about the correct
        // horizontal position, save parameters for later use.
        // (See Ref. 1)
        Fingering.length = 0
        var buf_voice = -1
        for (var i = 0; i < segment.annotations.length; i++) {
          var annotation = segment.annotations[i]
          if (Math.round(annotation.track/4) === staff &&
              annotation.type === Element.STAFF_TEXT) { // match staff text
            // check position of element. if x-pos. far off from tickm then ignore
            if ( annotation.textStyleType === TextStyleType.STAFF || 
                 annotation.textStyleType === TextStyleType.FINGERING) {
              if (Math.abs(annotation.pos.x) < 1.0 &&
                  (annotation.text >= "0" && annotation.text <= "5")) {
                annotation.textStyleType = TextStyleType.FINGERING
 
                // Todo: Find best matching note belonging to this text (only in x-direction)
                //   Easy for single note
                //   for chords find note with best match
                var distance = DMAX // initialize with fairly large number
                var buf_note = -1
                for (var voice = 0; voice < 4; voice++) {
                  if (segment.segmentType === Segment.ChordRest
                      && segment.elementAt(staff*4 + voice)
                      && segment.elementAt(staff*4 + voice).notes      
                     ) { // go through every chord
                    var notes = segment.elementAt(staff*4 + voice).notes
                    for (var j = 0; j < notes.length; j++) { // notes in chord: 1st pass
                      var note = notes[j]
                      var text_xp = annotation.pos.x
                      var note_xp = note.pos.x + note.bbox.width/2
                      var log = "  >>  >>  >>  "
                      log += "tk:" + segment.tick + ", "
                      log += "an:" + i + "(\"" + annotation.text + "\"), "
                      log += "st:" + staff + ", "
                      log += "vo:" + voice + ", "
                      log += "no:" + (notes.length - 1) + "-" + j + ", "
                      log += "(DIFF:" + r2(note_xp) + "-" + r2(text_xp) + "=" + r2(note_xp - text_xp)+ "), "
                      log += "TYPE:" + ((note.type === 12) ? "note" : "???")
                      //console.log(log)
                      if (Math.abs(note.pos.x + note.bbox.width/2 - annotation.pos.x) < distance) {
                        distance = Math.abs(note.pos.x + note.bbox.width/2 - annotation.pos.x)
                        buf_note = j
                        buf_voice = voice
                      }
                    }
                  }
                }
 
                // store found fingering staff text in array
                if (distance < DMIN && buf_note != -1) {
                  Fingering[Fingering.length] = {
                    id_st: i,
                    id_el: staff*4 + buf_voice,
                    id_nt: -1,
                    id_ne: -1,
                    id_co: buf_note,
                    staff: Math.round(annotation.track/4),
                    x_pos: annotation.pos.x,
                    y_pos: annotation.pos.y
                  }
 
                  var log = "at TICK:" + segment.tick + ",\t"
                  log += "found matching Staff text (\"" + annotation.text + "\")\t"
                  log += "while iterating over staff " + staff + " "
                  log += "at (x:" + Math.round(annotation.pos.x*100+5)/100 + ", "
                  log += ",y:" + Math.round(annotation.pos.y*100+5)/100 + "),\t"
                  log += "noteindex:" + buf_note + ", distance:" + r2(distance)
                  //console.log(log)
                }
              }
            }
          }
        }
 
        for (var voice = 0; voice < 4; voice++) {
          //cursor.voice = voice;
          //console.log("=  ..count: " + count + ", voice: " + voice)
          if (segment.segmentType === Segment.ChordRest
              && segment.elementAt(staff*4 + voice)
              && segment.elementAt(staff*4 + voice).notes      
              ) { // go through every chord
            var notes = segment.elementAt(staff*4 + voice).notes
            //console.log("=  ....number of notes_in_chord: " + notes.length + ", at tick:" + segment.tick)
            for (var i = 0; i < notes.length; i++) { // notes in chord: 1st pass
              // count total fingering elements in chord
              var note = notes[i]
              var noteElements = note.elements
              for (var j = 0; j < noteElements.length; j++) { // elements in note: 1st pass
                if (noteElements[j].type === Element.FINGERING) {
                  // for chords: find best matching fingering with certain note
                  var distance = DMAX // initialize with fairly large number
                  var buf_note = -1
                  for (var k = 0; k < notes.length; k++) {
                    if (Math.abs(notes[k].pos.x + notes[k].bbox.width/2 - noteElements[j].pos.x) < distance) {
                      distance = Math.abs(notes[k].pos.x + notes[k].bbox.width/2 - noteElements[j].pos.x)
                      buf_note = k
                    }
                  }
                  if (distance < DMIN && buf_note >= 0) {
                    // found best matching fingering with note in chord, assign to array
                    Fingering[Fingering.length] = {
                      id_st: -1,
                      id_el: staff*4 + voice,
                      id_nt: i,
                      id_ne: j,
                      id_co: buf_note,
                      staff: staff,
                      x_pos: noteElements[j].pos.x + notes[buf_note].pos.x,
                      y_pos: noteElements[j].pos.y + notes[buf_note].pos.x
                    }
                    //NB: note object: segment.elementAt[Fingering.id_el].notes[Fingering.id_nt]
                    var log = "at TICK:" + segment.tick + ",\t"
                    log += "found matching fingering text (\"" + noteElements[j].text + "\")\t"
                    log += "while iterating over staff " + staff + " "
                    log += "at (x:" + r2(noteElements[j].pos.x) + note.pos.x
                    log += ",y:" + (Math.round((noteElements[j].pos.y + note.pos.y) *100+5)/100  ) + "),\t"
                    log += "noteindex:" + buf_note + ", distance:" + r2(notes[buf_note].pos.x + notes[buf_note].bbox.width/2 - noteElements[j].pos.x)
                    //console.log(log)
                  }
                }
              }
              // *********************************************
              // *
              // *           3. calculate relative y point
              // *
              // *********************************************
              ya = Math.min(ya, note.pos.y - distToNote)
              // notes under a beam
              if (note.parent.beam && note.pos.y > note.parent.beam.bbox.y) {
                ya = Math.min(ya, note.parent.beam.bbox.y - distToBeam)
              }
              yb = Math.max(yb, note.pos.y + distToNote)
              // notes under a beam
              if (note.parent.beam && note.pos.y < note.parent.beam.bbox.y) {
                yb = Math.max(yb, note.parent.beam.bbox.y + note.parent.beam.bbox.height + distToBeam)
              } else if (note.parent.hook && note.parent.hook.pos.y > 4.25) {
                yb = Math.max(yb, distToBeam + note.parent.hook.pos.y)
              }
            }
          }
        }
 
        // *********************************************
        // *
        // *           4. Sort text vertically
        // *
        // *********************************************
 
        // sort fingering array from top to bottom
        // By using only quantized positions, above and/or below
        // the highest/lowest notes, we can keep the order even
        // after multiple calls. Last element/column must be the
        // y-position
        //
        // Sorting vertically will enable us to place fingers
        // in a stacked fashion at point 5.
        // (CURRENTLY NOT IMPLEMENTED)
 
        Fingering = Fingering.sort(function(a,b) {return (a.y_pos < b.y_pos) ? -1 : 1 }
        )
 
        // *********************************************
        // *
        // *           5. Move text
        // *
        // *********************************************
 
        // show array contents:
        if (Fingering.length > 0) {
          var log = "at TICK:" + segment.tick + "\n"
          for (var i = 0; i < Fingering.length; i++) {
            log += "\t\tCOUNT:" + Fingering.length + "-" + i + ",\t"
            if (Fingering[i].id_st == -1) {
              // handle NOTE/FINGERING text
              var nt = segment.elementAt(Fingering[i].id_el).notes[Fingering[i].id_nt]
              var t = nt.elements[Fingering[i].id_ne]
              var n_x = nt.pos.x + nt.bbox.width/2
              var n_y = nt.pos.y
              // align horizontally
              t.pos.x = n_x
              // align vertically
              t.pos.y = (((0.5 * nt.line + t.pos.y) < Y_MID_STAFF) ? ya : yb) - n_y
 
              log += "TEXT (\"" + nt.elements[Fingering[i].id_ne].text + "\")\t[" + r2(t.pos.x) + "," + r2(t.pos.y) + "]\t"
              log += "NOTE [" + r2(nt.pos.x) + "," + r2(nt.pos.y) + "]\t"
            } else {
              // handle STAFF FINGERING text
              var nt = segment.elementAt(Fingering[i].id_el).notes[Fingering[i].id_co]
              var t = segment.annotations[Fingering[i].id_st]
              var n_x = nt.pos.x + nt.bbox.width/2
              var n_y = nt.pos.y
              // align horizontally
              t.pos.x = n_x
              // align vertically
              t.pos.y = (t.pos.y < Y_MID_STAFF) ? ya : yb
 
              log += "TEXT (\"" + t.text + "\")\t[" + r2(t.pos.x) + "," + r2(t.pos.y) + "]\t"
              log += "NOTE [" + r2(nt.pos.x) + "," + r2(nt.pos.y) + "]\t"
            }
            log += "at (x:" + r2(Fingering[i].x_pos)
            log += ",y:" + r2(Fingering[i].y_pos) + ")"
            if (i < Fingering.length - 1) {
              log += "\n"
            }
          }
          //console.log(log)
        }
 
        segment = segment.next
 
      }
    }
  }
 
  // Round numbers to two digits.
  function r2(a) {
    return Math.round(a * 100) / 100
  }
 
  // 
 
  onRun: {
    if (typeof curScore === 'undefined')
      Qt.quit();
 
    applyToNotesInSelection()
 
    Qt.quit();
  }
 
}
software/repositiontext.txt · Last modified: 2019/02/17 02:48 by admin