Programando plugins para Musescore

musescore screen capture

Estos días, vía petición de un gran amigo, hemos estado jugando con MuseScore para desarrollar un plugin que realizara por nosotros una serie de modificaciones en las notas marcadas de una determinada manera. El objetivo es demostrar que cambiando la forma en la que que suenan las notas se puede realizar una aproximación más que aceptable en una escala diferente a la estándar.

Musescore es una software open source de escritorio que permite componer obras musicales de manera tradicional escribiendo notas sobre un pentagrama.

Dispone de un editor WYSIWYG con soporte completo para reproducir partituras e importar o exportar MusicXML y archivos MIDI estándar. Tiene soporte para notación de percusión, así como impresión directa desde el programa.

En su versión 2.3.2 utilizada en este proyecto, permite ampliar las funcionalidades que tiene o cambiar el comportamiento estándar del programa mediante la ejecución de plugins de terceros.

Los plugins de terceros son ficheros de texto en sintaxis QML, una variante de Javascript creado por los programadores de las librerías gráficas Qt que permite hacer binding entre las propiedades de los objetos del script y el entorno gráfico. Es decir, que al modificar una propiedad de un objeto se reflejará automáticamente el comportamiento de este en la ejecución. Y al revés, al ejecutar el programa el script podrá obtener los diferentes valores de las propiedades así como avance la ejecución.

Musescore está programado en C++ y utilizando el entorno gráfico multiplataforma y open source Qt.

Por tanto desde QML se puede escribir un script utilizando la sintaxis QML y con sentencias Javascript que pueden interactuar sobre los objetos de configuración de la partitura y configurar cómo Musescore interpretará los sonidos.

Para ello Musescore ofrece a los plugins una serie de objetos, con métodos y propiedades, para que a través de sentencias se invoquen a objetos, llamen a métodos o escriban y lean de las propiedades, con el fin de  alterar el comportamiento de la obra o del programa. De manera muy similar al navegador web, que vía el estándar Ecmascript del consorcio W3C, ofrece un objeto Javascript denominado Window para poder interactuar con páginas Web. En este caso MuseScore pone a disposición de los plugins objetos como Note o Tremolo entre otros.

Desde la herramienta de Plugin Creator, un editor de texto con resaltado de sintaxis QML desde el propio Musescore, se puede acceder fácilmente a la referencia completa de objetos disponibles para la elaboración de los plugins.

Estructura de un plugin

La estructura del plugin viene determinada por QML y el punto de entrada siempre es el objeto MuseScore (MS). Este objeto tiene una función que ejecutará siempre MS en el momento que se active el plugin: onRun.

Por tanto el plugin a desarrollar deberá tener la siguiente estructura básica:

import ... // obviado por simplicidad

MuseScore {
   ... // obviado por simplicidad

   onRun: {
      ... // sentencias a ejecutar
   }
}

Dentro del método onRun se deben indicar todas las sentencias a ejecutar por el plugin. Además, el objeto MuseScore permite indicar otras tantas configuraciones acerca del plugin, como por ejemplo cómo se va a mostrar este plugin en el gestor de plugins:

...
MuseScore {
   menuPath: "Plugins.MyPlugin"
   description: "My awesome plugin"
   version: "1.0"
   onRun: {
      ... // sentencias a ejecutar
   }
   // otras definiciones: propiedades, métodos, ...
}

Aunque no se explica en este texto, existe la posibilidad de configurar diálogos para la interfaz de usuario que permiten al compositor indicar parámetros de entrada a los plugins. Desde QML se pueden indicar ventanas, añadir campos de texto, etiquetas y otros componentes visuales para mejorar la experiencia del usuario y al mismo tiempo configurar el plugin en el momento de ejecución.

Volviendo al método onRun, es posible realizar llamadas de este a otras funciones que estén definidas dentro del objeto MuseScore. Así como también acceder a variables globales.

MuseScore {
   ... // propiedades

   onRun: {
      myFunc();
      Qt.quit(); //debe respetarse esta última invocación.
   } 
 
   property varian myMatrix: [
      [1, 2, ...], // abreviado por simplicidad
      [5, 8..],
      ...
   ]

   function myFunc(){
      // awesome stuff
      var value = myMatrix[0][1];
   }
}

En el ejemplo de la tabla puede verse cómo es posible definir una función que será llamada desde onRun. Al mismo tiempo se ha definido una matrix (myMatrix) que es accesible por todas las funciones y métodos del objeto MuseScore.

Los programadores de Javascript notarán que la sintaxis es muy parecida y por tanto la curva de aprendizaje será muy reducida.

Plugin Retune

Para el proyecto que se describe, será suficiente con la ejecución del plugin en sí mismo sin necesidad de preguntar al usuario por datos adicionales a la propia partitura en sí.

El objetivo del plugin es modificar cómo suena cada una de las notas del pentagrama cuando estas estén adulteradas por una marca visual, denominada en inglés un Accidental.

El pseudo código que describe el algoritmo a implementar es el siguiente:

Para cada nota de la partitura:
   Obtener su modificación (Accidental)
   Si no tiene:
      pasar a la siguiente nota
   Si tiene:
      Convertir el accidental en un índice (i)
      Convertir la nota en un índice (j)
      Consultar una matriz global de ajustes de nota
      para la nota j y el accidental i -> ajuste(i,j)
      Si existe ajuste(i,j):
         modificar nota.
      Si no, continuar.

El algoritmo es muy sencillo y sólo requiere definir la matriz de ajustes y recorrer cada una de las notas para realizar la modificación.

La modificación de la nota se debe realizar cambiando la propiedad tuning del objeto Note de MuseScore. La matriz a usar se ha diseñado específicamente para que la modificación a realizar pueda asignarse directamente a la citada propiedad de Note:

var actualNote = // get Note
actualNote.tuning = matrix[i][j];

La parte compleja del plugin es hacer el recorrido de las notas de la partitura. Para realizar dicho recorrido debe usarse el objeto Cursor accesible vía una propiedad denominada curScore del objeto MuseScore:

...
MuseScore{
   onRun: {
      var cursor = curScore.newCursor();
      cursor.rewind(0);
   }
}

El método rewind acepta un entero con los valores posibles siguientes: 0, principio de la partitura. 1 principio de la selección. Y 2 final de la selección.

Una partitura puede estar compuesta por varias voces o instrumentos y a su vez organizado en pentagramas. Esta estructura obliga a que el recorrido de notas debe hacerse para todas la voces o instrumentos y para todos los pentagramas.

El número de pentagramas se puede obtener consultando curScore.nstaves. Mientras que para el número de voces hemos supuesto 4. Teniendo en cuenta lo citado hasta ahora el recorrido quedaría de la siguiente manera:

...
MuseScore{
   onRun: {
   var cursor = curScore.newCursor();
   cursor.rewind(0);
   var startStaff = 0;
   var endStaff = curScore.nstaves - 1; 
   for (var staff = startStaff; staff <= endStaff; staff++) {
     for (var voice = 0; voice < 4; voice++) {
       cursor.rewind(1); // sets voice to 0
       cursor.voice = voice; //voice has to be set after goTo
       cursor.staffIdx = staff;
       cursor.rewind(0) // if no selection, beginning of score
       while (cursor.segment) {
         if (cursor.element) {
           if (cursor.element.type == Element.CHORD) {
             var graceChords = cursor.element.graceNotes;
             for (var i = 0; i < graceChords.length; i++) {
               // iterate through all grace chords
               var notes = graceChords[i].notes;
               for (var j = 0; j < notes.length; j++)
                 tuneNote(notes[j]);
             }
             var notes = cursor.element.notes;
             for (var i = 0; i < notes.length; i++) {
               var note = notes[i];
               tuneNote(note);
             }
           }
         }
         cursor.next();
       }
     } 
   }
}

Este recorrido es casi una constante dentro del desarrollo de plugins de MuseScore, ya que es bastante frecuente querer recorrer las notas del pentagrama para realizar diversas funcionalidades.

Una vez obtenida cada nota, el último paso es realizar la modificación de tuning sobre esta en función del Accidental que le acompañe.

El objeto Note de QML en MuseScore representa la nota que se está revisando en ese momento según el bucle anterior (propiedad tpc: tonal pitch class). Dentro de él está la propiedad tuning que se debe modificar como objetivo del plugin. También en Note, está la propiedad que indica si tiene o no un Accidental, y si es así cuál es. Por tanto lo único que resta hacer es convertir la nota y el accidental en índices para acceder a matrix.

Para la conversión de la nota en un índice se ha utilizado la siguiente relación:

// función tpcToIdx
switch(note.tpc){
 case 14: return 0; //c
 case 16: return 1; //d
 case 18: return 2; //e
 case 13: return 3; //f
 case 15: return 4; //g
 case 17: return 5; //a
 case 19: return 6; //b
 default: 
   return null;     
}

Mientras que para la conversión de los Accidentals en el otro índice se ha utilizado esta otra conversión:

// función accToIdx
if (note.accidental != null){
 switch(note.accidental.accType){
 case Accidental.FLAT_SLASH2: return 1; 
 case Accidental.FLAT_SLASH: return 2; 
 case Accidental.FLAT_ARROW_DOWN: return 3; 
 case Accidental.NATURAL: return 4;
 case Accidental.SHARP_SLASH: return 5; 
 case Accidental.SHARP_ARROW_DOWN: return 6; 
 case Accidental.SHARP_ARROW_UP: return 7; 
 case Accidental.SHARP_SLASH2: return 8; 
 case Accidental.SHARP_SLASH3: return 9; 
 case Accidental.SHARP_SLASH4: return 10; 
 default: return -1;
 }

Por tanto, la aplicación de la modificación final a la nota debe comprobar que se han obtenido índices mayores iguales a 0. El valor resultante de la matriz debe ser diferente de null. Si se cumplen ambas condiciones la nota debe ser modificada:

function tuneNote(note) {
  var result = null;
  var ai = accToIdx(note);
  var ti = tpcToIdx(note);
  if (ai >=0 ){
    result = matrix[ai][ti];
  } else if (ai == -1){
    note.accidental.color = "#FF0000"
  }
  return result;
}

Para facilitar la comprensión del resultado de la ejecución del plugin se ha realizado la modificación del accidental en color rojo si se detecta una combinación incorrecta de índices sobre la matriz.

Dejamos a vuestra disposición el código del plugin en Github habiendo eliminado los valores de la matriz por protección del trabajo a realizar.

Para comprobar su funcionamiento basta indicar algunos valores en la matriz y desde el Plugin Manager de MuseScore ejecutarlo sobre una partitura cualquiera que tenga algunos Accidentals puestos, ya que la modificación de las notas sólo sea hará en aquellas que tengan un Accidental.

¡Hacednos llegar vuestros comentarios!

Autor: Ramón Arnau

Director de Arteco Consulting sl. Ingeniero Informático. Máster en Administración y Dirección de Empresas. Máster en Tecnologías de la Información. Auditor ISO 27001. ITIL.

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *