LilyPond: cómo escribir un archivo MAKE básico

Durante los últimos años me enseñé a escribir partituras usando LilyPond y además a facilitar el manejo de archivos usando scripts de Bash . Recientemente comencé un proyecto un poco más grande que solo una o dos páginas y una vez más me encontré con el artículo sobre Makefiles en el documental de LilyPond .

Aunque al leer este artículo me resultó un poco difícil comprender la técnica real detrás de la plantilla, Makefiletambién porque el artículo no proporciona el repositorio de archivos con el que está trabajando.

Así que pensé en tomarme el tiempo para inventar un proyecto de ejemplo para preguntar cómo Makefilepodría ser una en este escenario. Cómo lo construiría paso a paso y cómo ejecuto el Makefile. ( Editar: refiriéndose a una página de manual de creación, entendí que se puede decir que uno usa makecomo intérprete como bashen un shell-script.sh. El comando simplemente parece make -f Makefileejecutarse en el directorio raíz del proyecto).


El proyecto tiene una estructura de archivos como esta:

├── Book.ly
├── Book.pdf
├── global-files
│   ├── copyright.ily
│   ├── Frontpage.ily
│   ├── header.ily
│   └── paper.ily
├── input-files-voiceI
│   ├── Nr_01-voiceI.ily
│   ├── Nr_02-voiceI.ily
│   └── Nr_03-voiceI.ily
├── input-files-voiceII
│   ├── Nr_01-voiceII.ily
│   ├── Nr_02-voiceII.ily
│   └── Nr_03-voiceII.ily
├── README.md
├── single-pages-voiceI
│   ├── MIDI
│   │   ├── Score-Nr_01-voiceI.midi
│   │   ├── Score-Nr_02-voiceI.midi
│   │   └── Score-Nr_03-voiceI.midi
│   ├── PDF
│   │   ├── Score-Nr_01-voiceI.pdf
│   │   ├── Score-Nr_02-voiceI.pdf
│   │   └── Score-Nr_03-voiceI.pdf
│   ├── Score-Nr_01-voiceI.ly
│   ├── Score-Nr_02-voiceI.ly
│   └── Score-Nr_03-voiceI.ly
├── single-pages-voiceI_a_II
│   ├── MIDI
│   │   ├── Score-I_u_II_Nr_01.midi
│   │   ├── Score-I_u_II_Nr_02.midi
│   │   └── Score-I_u_II_Nr_03.midi
│   ├── PDF
│   │   ├── Score-I_u_II_Nr_01.pdf
│   │   ├── Score-I_u_II_Nr_02.pdf
│   │   └── Score-I_u_II_Nr_03.pdf
│   ├── Score-I_u_II_Nr_01.ly
│   ├── Score-I_u_II_Nr_02.ly
│   └── Score-I_u_II_Nr_03.ly
└── single-pages-voiceII
    ├── MIDI
    │   ├── Score-Nr_01-voiceII.midi
    │   ├── Score-Nr_02-voiceII.midi
    │   └── Score-Nr_03-voiceII.midi
    ├── PDF
    │   ├── Score-Nr_01-voiceII.pdf
    │   ├── Score-Nr_02-voiceII.pdf
    │   └── Score-Nr_03-voiceII.pdf
    ├── Score-Nr_01-voiceII.ly
    ├── Score-Nr_02-voiceII.ly
    └── Score-Nr_03-voiceII.ly

Los archivos de entrada de ambas voces tienen un formato como este:

\relative c {
  \clef bass
  \time 3/4
  \key c major

  c4( d e f      | %01
  g1) \bar "|."  | %02
}

Los archivos de partitura tienen el propósito de compilarse para la salida de PDF y MIDI. y simplemente tenga este aspecto (a pesar del hecho de que los Scoresdos sistemas contienen otro Staff):

\version "2.18.2"

#(set-default-paper-size "a4")
#(set-global-staff-size 22)

\include "../global-files/header.ily"

\score {
  \new StaffGroup = "" \with {
    instrumentName = \markup { \bold \huge { \larger "1." }}
  }
  <<
    \new Staff = "celloI" \with { midiInstrument = #"cello" }

    \include "../input-files-voiceI/Nr_01-voiceI.ily"
  >>
  \layout {}
  \midi {}
}

La parte del libro es la parte con la que todavía estoy bastante descontento. Preferiría esto bastante simple con solo usar los Score*.lyarchivos como \includes, pero tengo problemas con los \includesque ya están en los Score.lyarchivos, ya que no solo contienen el \scorebloque para ser compilables por ellos mismos.

Bueno, me vendría bien \bookestablecer un nombre de salida de libro como \bookOutputSuffix "OutputName", pero luego Book.lyse convertiría en un archivo enorme, que tardaría mucho tiempo en compilarse, incluso para un pequeño cambio en una sola pieza.

Así que en este momento mi Book.lyarchivo tiene el siguiente formato y el único propósito de compilar todo el libro con dos voces en dos pentagramas, pero con todas las piezas, aquí 01-03:

\version "2.18.2"

#(set-default-paper-size "a4")
#(set-global-staff-size 22)

\include "./global-files/paper.ily"

\book {

  \include "./global-files/Frontpage.ily"

  %%%% Score Number: 1 ==================================%%%%

  \score {
    \new StaffGroup = "" \with {
      instrumentName = \markup { \bold \huge { \larger "1." }}}
    <<
      \new Staff = "voiceI" \with { midiInstrument = #"voice" }
      \include "./input-files-voiceI//Nr_01-voiceI.ily"
      \new Staff = "voiceII" \with { midiInstrument = #"voice" }
      \include "./input-files-voiceII//Nr_01-voiceII.ily"
    >>
    \layout {
      \printTupletBow
    }
  }

  %%%% Score Number: 2 ==================================%%%%

  \score {
    \new StaffGroup = "" \with {
      instrumentName = \markup { \bold \huge { \larger "2." }}}
    <<
      \new Staff = "voiceI" \with { midiInstrument = #"voice" }
      \include "./input-files-voiceI//Nr_02-voiceI.ily"
      \new Staff = "voiceII" \with { midiInstrument = #"voice" }
      \include "./input-files-voiceII//Nr_02-voiceII.ily"
    >>
    \layout {}
  }

  %%%% Score Number: 3 ==================================%%%%

  \score {
    \new StaffGroup = "" \with {
      instrumentName = \markup { \bold \huge { \larger "3." }}}
    <<
      \new Staff = "voiceI" \with { midiInstrument = #"voice" }
      \include "./input-files-voiceI//Nr_03-voiceI.ily"
      \new Staff = "voiceII" \with { midiInstrument = #"voice" }
      \include "./input-files-voiceII//Nr_03-voiceII.ily"
    >>
    \layout {}
  }
}

Mi flujo de trabajo es el siguiente:

  1. Escribo los archivos de entrada:input-file.ily
  2. Ejecuto un bash-script.shque crea los archivos compilables Score.lydesde elinput-files/*.ily
  3. Ejecuto un bash-script.shque crea el Book.lyarchivo compilable desde elinput-files/*.ily
  4. Compilo los Score.lyarchivos uno por uno o ejecuto un for file in *.ly; do lilypond "$file"; doneciclo simple, pero en cada uno de los tres directorios de Score. Uso un script para mover los archivos PDF y MIDI a sus carpetas correspondientes.
  5. Simplemente corro lilypondpara compilar el Book.lyarchivo.

HECHO


El proyecto real para el que se hace esta pregunta se puede encontrar aquí en GitHub


Actualización 1:

mi sistema:

    Operating System: Debian GNU/Linux bullseye/sid
              Kernel: Linux 5.3.0-2-686-pae
        Architecture: x86
        GNU LilyPond: 2.18.2
My Editor - GNU Nano: 4.5
      Guake Terminal: 3.6.3
            GNU Make: 4.2.1

Agregué my shell-scriptsa un Git-Repository separado


Actualización 2:

Este es un gráfico muy simplificado de las dependencias. Suponiendo que solo hubiera uno voice:

./infiles/                        
  infile{01..03}.ily -------------> ./Book.ly ===> Book.pdf
     |                                 ^ ^ ^
     *---------> Scores{01..03}.ly === | | |=====> Score{01..03}.pdf
                      ^  ^         === | | |=====> Score{01..03}.midi
                      |  |             | | |
./global-files/       |  |             | | |
  header.ily    ------*  |             | | |
  copyright.ily ---------+-------------* | |
  Frontpage.ily -------------------------* |
  paper.ily     ---------------------------*
¿Tiene los archivos bash-script.sh disponibles en algún lugar? Tengo algunas dificultades para asimilar todo el procedimiento.
También informe a abt OS y al editor de su elección.
FYI: actualicé mi respuesta para proporcionar más contexto y una breve descripción técnica de la mecánica.
Esta pregunta se estaba discutiendo en el meta .

Respuestas (2)

Invocación

makebuscará en el directorio actual un archivo llamado Makefileo makefile, por lo que a menudo es más simple nombrarlo con una de estas dos opciones y luego invocarlo con el comando simple:

$ hacer

Si usa la 'M' mayúscula, el archivo generalmente aparecerá en la parte superior de acuerdo con el orden alfabético o de intercalación.

Normas

makeopera mediante el uso de reglas sobre cómo crear una salida a partir de una entrada. El formato de una regla es el destino seguido de dos puntos, luego una lista de dependencias delimitada por espacios, seguida de comandos sangrados con TAB.

objetivo: dependencias
    comandos

El destino o las dependencias pueden ser nombres de archivo o símbolos que correspondan a otras reglas.

Si no especifica un objetivo en la línea de comando, invocará la primera regla que encuentre. Entonces, una técnica común es hacer que la primera regla sea una regla "ficticia" que no produce un archivo sino que simplemente recopila todos los pasos o resultados juntos. P.ej.

todo: salida1

La invocación makecon este archivo MAKE intentará crear output1si no existe. Si hay una regla para crear output1más adelante en el archivo MAKE, la usará.

Para su caso, sugiero hacer una regla de nivel superior para crear Score.lyyBook.ly

todo: Score.ly Book.ly

Reglas de patrón

Para reemplazar su ciclo de shell, puede usar una regla de patrón.

%.pdf: %.ly
    estanque de lirios $^

Esta regla dice: Para crear un archivo .pdf, ejecútelo lilyponden el archivo .ly correspondiente .

Tenga en cuenta que el comando a ejecutar debe comenzar con un carácter TAB literal. La%^variable se refiere al archivo de entrada mencionado anteriormente. Otras variables útiles son$@para el destino y$<para la primera entrada si hay más de una.

Esto maneja solo una parte de su ciclo de shell, definiendo la transformación de un archivo de entrada a un archivo de salida. Para la otra tarea de generar una lista de archivos, hay algunas variables especiales disponibles en GNU make para esto.

entradas= $(notdir $(comodín ./*.ly))
bases= $(nombre base $(entradas))
salidas= $(patsubst %,%.pdf,$(bases))

Después de estas definiciones, puede usar $(outputs)como una dependencia en una regla, como:

pdf: $(salidas)
    mkdir -p PDF
    mv *.pdf PDF
    mkdir -p MIDI
    mv *.midi MIDI

Este ejemplo de los documentos de LilyPond muestra que puede colocar varios objetivos a la izquierda de los dos puntos, por lo que puede tener en cuenta ambos tipos de archivos producidos por LilyPond, lo que no hace mi ejemplo aquí. Nuevamente, cada uno de estos comandos debe tener una sangría con un carácter TAB ASCII de buena fe.

Para casos muy sencillos

Puede usar un archivo MAKE solo para recopilar uno o más scripts de shell juntos, ignorando gran parte de la complejidad de las reglas y dependencias si su situación es muy simple.

Para un proyecto en el que hice un montón de archivos .abc en un solo directorio, todo se maneja con una sola regla:

all:
    for f in `ls *.abc` ;               \
    do ../abcm2ps -O $${f%.abc}.ps $$f ; \
       ps2pdf $${f%.abc}.ps ;           \
    done ;
    zip -r evildead.zip *.pdf
Eso no funcionó. Probando <tty><pre>.
Funciona ahora. Sin embargo, una pestaña inicial cargada semánticamente es tan antinatural, no solo para los humanos sino también para los editores bien intencionados, ¿¿que puede ser necesaria una advertencia más fuerte?
Verdadero. Así makeha funcionado siempre. Pero trataré de marcarlo más fuerte y con más frecuencia a medida que complete la respuesta.
Encontré que esto en LilyPond Docs parece la forma de manejar ambos formatos PDF y MIDI , por lo que sé, bashtambién parece que usan un if statementpara averiguar si los archivos existen o no y luego ejecutan el mvcomando en él.
En lugar de lilypond $^, usaría lilypond $<. Esto le permite usar \includey especificar las dependencias en el archivo MAKE.
@Rusi La tradición (tm) dice que el problema con las pestañas se notó solo unas semanas después de que se lanzó la utilidad por primera vez (en la década de 1970) y no se cambió porque ya había más de una docena de usuarios a los que les habría incomodado el cambio.
@luserdroog agregué un gráfico :-)

La respuesta de luser droog da una buena visión general de makesí misma. Aquí hay un ejemplo de cómo aplicar eso a un proyecto de Lilypond del mundo real con las siguientes características:

  • Múltiples partes: piano, fagot, etc.
  • Múltiples movimientos.
  • Múltiples salidas deseadas: una sola partitura "maestra" (director), una partitura maestra para cada movimiento y una partitura de parte para cada instrumento.
  • Ambas salidas PDF y MIDI.
  • Definiciones comunes (macros) para música y presentación.

Por ejemplo, si su Book.lyarchivo incluye su Nr_01-voiceI.ilyarchivo, que a su vez incluye su macros.ilyarchivo de definiciones compartidas, entonces, si macros.ilyse cambia, su Makefile necesita saber que necesita recompilarse Book.lypara actualizarse Book.pdf.

Por otro lado, si cambias solo una parte, es muy conveniente poder escribir make partsy hacer que Make solo recompile la parte que se cambió, en lugar de perder el tiempo recompilando todas las demás. Y, si solo desea escuchar un segmento, debería poder make midiomitir la composición tipográfica lenta y solo volver a hacer la secuencia necesaria.

He escrito un script de shell para generar un Makefile que tiene todas estas propiedades. En particular, recorre automáticamente el \includegráfico de su proyecto para determinar qué archivos de Lilypond dependen de cuáles otros, e incurrirá en el conjunto mínimo de recompilaciones para cualquier cambio en particular. Simplemente dígale qué "archivos principales" tiene y cuáles quiere en formato PDF y/o MIDI, y él hará el resto.

Aquí está el guión: https://github.com/MutopiaProject/MutopiaProject/blob/918971593735f2dbf4864f289767b8d59a7d950e/ftp/MozartWA/KV488/Mozart-KV488/Mozart-KV488-lys/create_makefile.sh

Aquí hay un ejemplo de la salida Makefile: https://github.com/MutopiaProject/MutopiaProject/blob/918971593735f2dbf4864f289767b8d59a7d950e/ftp/MozartWA/KV488/Mozart-KV488/Mozart-KV488-lys/Makefile

El mecanismo central es crear un destino secundario para cada archivo de Lilypond, al que he llamado su archivo "lydep" ("dependencias de Lilypond"), de modo que el lydep de un archivo está sucio si y solo si alguna de las fuentes transitivas del archivo está sucia. . En términos prácticos, esto significa que cada destino de Lydep debe depender de su archivo fuente más todos los destinos de Lydep de los que tiene \includedependencias directas. Luego, la resolución automática de Make se encarga del resto.

La secuencia de comandos se adapta a un proyecto en particular, pero debería poder mantener la infraestructura central en su lugar y personalizar la makefilefunción en la parte inferior para sustituirla en la estructura de su proyecto.

Escribí este script para GNU/Linux, y es posible que necesite algunos ajustes menores para macOS/BSD (por ejemplo, cambiar readlink -f), pero en general es bastante sencillo. Requiere bashy GNU make(para objetivos secundarios). Veo que está en Debian, por lo que debería funcionar bien como está.

Todo esto se publica bajo la licencia MIT. Por favor, siéntase libre de tomar lo que encuentre útil.

Yuhu man, estas son un par de líneas para leer. ¡Gracias por la respuesta y por compartir tu trabajo! <3
Cosa segura. Veré si puedo explicar/resumir la esencia de esto cuando tenga la oportunidad más tarde hoy.