2

After spending long enough learning lilypond and writing scores for multiple voices, I've often run into the problem of generating individual voice midi/mp3 files from the score. I was surprised to find this was not already a feature or that no one had an easy way to do this since individual voice mp3s are a precious resource for learning a multi-voice piece !

While it is possible to just remove other voices from the score temporarily, compile, rename the midi, change to another voice, compile, rename, etc.. This becomes very tedious very fast. Plus, I find it helpful to still have the other voices at low volume in the background to have a sense of the harmony.

So I need a way to generate these separate voices from a standard multi-voice score.

Here is a standard lilypond script for a 4-voice example to use as a test file :

\version "2.18.2"

global = {
  \key c \major
  \time 4/4
  \tempo "Allegro" 2 = 60
}

% Lyrics
verseBass = \lyricmode {Ho Ho Hoo, Ho Ho Hoo !
}
verseTenor = \lyricmode {Ha Ha Haa, Ha Ha Haa !
}
verseAlto = \lyricmode {Huu Hu Hu, Hu Hu Huu !
}
verseSoprano = \lyricmode {Hi Hi Hii, Hi Hi Hii !
}

% Music
voiceBass = \relative c {\global c4 b f'2 d4 g, c2 \bar "|."}
voiceTenor = \relative c' {\global g4 b c2 b4 f g2}
voiceAlto = \relative c' {\global e2 d4 g g b, e2}
voiceSoprano = \relative c'' {\global c8e d4 a2 d4 b c2}

% Score stucture
voicePartBass = \new Staff \with {
  instrumentName = "Bass"
  shortInstrumentName = "B"
  midiInstrument = "violin" 
} {
  
  \clef bass \voiceBass   
} \addlyrics { \verseBass }

voicePartTenor = \new Staff \with {
  instrumentName = "Tenor"
  shortInstrumentName = "T"
  midiInstrument =  "violin" 
} { 
  
  \clef "G_8" \voiceTenor
} \addlyrics {\verseTenor}

voicePartAlto = \new Staff \with {
  instrumentName = "Alto"
  shortInstrumentName = "A"
  midiInstrument = "violin" 
} {
  
  \voiceAlto   
} \addlyrics { \verseAlto }

voicePartSoprano = \new Staff \with {
  instrumentName = "Soprano"
  shortInstrumentName = "S"
  midiInstrument =  "violin" 
} { 
  
  \voiceSoprano
} \addlyrics {\verseSoprano}


\score {
    <<
      \new ChoirStaff << % choirstaff adds the fancy join on voice staves
        \voicePartSoprano
        \voicePartAlto
        \voicePartTenor 
        \voicePartBass
      >>
    >>
    \layout {}
    \midi {}
}

Which outputs

enter image description here

And a midi file with everything.

4

TL;DR : You add some tags and there's a bash script at the end which automates the separate midi part generation (for Unix users only, sorry :P)

The midiMaximumVolume option

In my quest for a multi-voice split, I found out about an option called midiMaximumVolume which can be set within a Staff environment. By setting this variable to a value between 0 and 1, it's possible to tone down (or entirely suppress) the volume of the staff it's placed within.

This needs to be set for each voice, so it's about as tedious as removing the music variables of other parts and then you still have to do the compiling/renaming shenanigans.

However it's already a great step towards automation of split-voices since it is a single parameter that can be targeted (instead of any custom \myvoice variables one may use...)

Plus, this makes it possible to have the other voices at a low volume in the background by setting them to a lower volume (eg. main voice at 1, others at 0.3).

Introducing a tag system

So the idea is to tag each part in its staff with a name & tag so we can easily detect where to introduce the volume parameter as well as a name for each part (mainly for file-naming). I went with something like this :

%#%part:<part_name>

Which fits in a lilypond comment. It must be placed at the location where the volume option would be set. Using the example above, it would be like so :

% Score stucture
voicePartBass = \new Staff \with {
  instrumentName = "Bass"
  shortInstrumentName = "B"
  midiInstrument = "violin" 
} {
  %#%part:Bass
  \clef bass \voiceBass   
} \addlyrics { \verseBass }

voicePartTenor = \new Staff \with {
  instrumentName = "Tenor"
  shortInstrumentName = "T"
  midiInstrument =  "violin" 
} { 
  %#%part:Tenor
  \clef "G_8" \voiceTenor
} \addlyrics {\verseTenor}

Bash magic

Now we have to target each tag and change it into the appropriate commands to use its voice as the main voice with the others in background, and repeat for all tags.

I made this into a bash function ly2partmidi (at the end of the post).

You can now source the function and run

$ ly2partmidi test.ly

which generates test_<part_name1>.midi, test_<part_name2>.midi, etc. in one go !

To modulate the volume (or silence) voices :

$ ly2partmidi <file.ly> <mainvolume> <othervolume>

eg. to remove other voices completely ly2partmidi <file.ly> 1 0 or to have all but the main voice 0 1, default is 1 0.3.

If we set up the test.ly file above properly, and run the function, it generates test_Soprano.midi, test_Alto.midi, test_Tenor.midi and test_Bass.midi files, as expected.

You can also add a midi2mp3 step in there, if you want.

The bash function :


ly2partmidi () {
    lyfile=${1}
    volumeMain=${2:-1} # level of the 'main' voice of a file
    volumeOther=${3:-0.3} # level of the other voices (set to 0 if completely silent)
    tagregex="^\s*%#%part:([a-zA-Z0-9_\-]+)"
    setvolcmd="\\\set midiMaximumVolume = #"
    
    # find the tags in the .ly file
    mapfile -t fulltags < <(  grep -Po ${tagregex} ${lyfile} )
    tagnames=()
    
    for t in ${fulltags[@]} ; do
        t1=$(echo $t | sed -E "s/${tagregex}/\1/")
        tagnames+=("$t1") 
    done
    
    # get unique of each tag
    fulltags=($(echo "${fulltags[@]}" | tr ' ' '\n' | sort -u | tr '\n' ' '))
    tagnames=($(echo "${tagnames[@]}" | tr ' ' '\n' | sort -u | tr '\n' ' '))
    
    # for each tag...
    for i in ${!fulltags[@]}; do
        ft=${fulltags[$i]}
        t=${tagnames[$i]}
        
        echo -e "\n--- Generating part [$t] ..."
        echo -e "\tCopying lilypond script"
        # make temp copy of .ly
        t_lyfile=${lyfile/.ly/_$t.ly}
        cp -f $lyfile $t_lyfile
        
        echo -e "\tReplacing tags with midiVolume cmd"
        # replace tags with the midiVolume cmds
        sed -i -E "s/${ft}/${setvolcmd}${volumeMain}/g" $t_lyfile
        sed -i -E "s/${tagregex}/${setvolcmd}${volumeOther}/g" $t_lyfile
        
        echo -e "\tRunning lilypond engraving to generate midi"
        # generate midi
        lily_o=${t_lyfile/.ly/}
        lilypond -s -dno-print-pages -o $lily_o $t_lyfile
        
        rm $t_lyfile
        
    done
}

3

Your solution obviously works, but you don't need any compile/rename/rinse/repeat shenanigans.

You can put multiple \score blocks in one file. Each of those blocks will typeset the music into some file if it has the \layout block inside of it, and it will put MIDI into some file if it has the \midi block inside of it. So you can add another score block that would look along these lines:

\score {
    \new Staff \with {
        midiInstrument = "saxophone"
        midiMinimumVolume = #1
        midiMaximumVolume = #1
    } \new Voice \soprano
    \new Staff \with {
        midiInstrument = "acoustic grand"
        midiMinimumVolume = #0.4
        midiMaximumVolume = #0.4
    } \new Voice \alto
    % etc for \tenor and \bass
    % NO \layout block!!
    \midi {}
}

This will make a file your-filename-0.midi with your soprano played by a loud saxophone and the other parts by a quiet piano. You didn't put a \layout block in it, so it will typeset no music, just make the MIDI. Rinse & repeat for the other three voices.

Of course you can further automate this. I use this function (which I pilfered somewhere years ago, I don't remember where):

rehearsalMidi = #
(define-music-function
 (parser location name midiInstrument lyrics) (string? string? ly:music?)
 #{
     \unfoldRepeats <<
         \new Staff = "sopran" \new Voice = "sopran" { s1*0\f \sopran }
         \new Staff = "alt" \new Voice = "alt" { s1*0\f \alt }
         \new Staff = "tenor" \new Voice = "tenor" { s1*0\f \tenor }
         \new Staff = "bas" \new Voice = "bas" { s1*0\f \bas }
         \context Staff = $name {
             \set Score.midiMinimumVolume = #0.5
             \set Score.midiMaximumVolume = #0.5
             \set Score.tempoWholesPerMinute = #(ly:make-moment 100 4)
             \set Staff.midiMinimumVolume = #1.0
             \set Staff.midiMaximumVolume = #1.0
             \set Staff.midiInstrument = $midiInstrument
         }
         \new Lyrics \with {
             alignBelowContext = $name
         } \lyricsto $name $lyrics
     >>
 #})

Now I just add blocks like this,

\book {
    \bookOutputSuffix "sopran"
    \score {
        \rehearsalMidi "sopran" "soprano sax" \slovaI
        \midi { }
    }
}

one for each voice. The three arguments to \rehearsalMidi are: the voice whose part it is, the instrument that part should be played on, and the lyrics to go with that. Since we call the four voices soprán, alt, tenor and bas in Czech, I use these names for my voices, but you will surely be able to tinker with the function to make it meet your needs. The book and bookOutputSuffix just make the file names nicer, e. g. my-filename-sopran.midi, my-filename-alt.midi etc. instead of my-filename-0.midi etc.

By the way, when you write this one time, you can copy&paste it into all of the choral music you typeset (which is what I'm doing several years already :)).

3
  • Wow, that's a very good approach as well ! I'm not experienced enough in lilypond's language to make functions like yours, so I went with an 'outside' approach to automation and keep lilypond only for the music.
    – RoB
    Dec 14 '20 at 8:34
  • I have a question on your function, though : If it does not typeset any music, what's the point of having the lyrics added ?
    – RoB
    Dec 14 '20 at 8:36
  • @RoB, the function is just a "source code generator", it just writes a larger block of source code. Nothing complicated. // The lyrics get embedded into the MIDI somehow. I guess that some programs can then make advantage of it (esp. programs that "convert" MIDI back to score).
    – Ramillies
    Dec 14 '20 at 13:34

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.