Hi everybody!
I think some people might be interested in the files attached.
JSBI1.ly is a sample document (Bach Invention No. 1) that demonstrates the
syntax.
It includes some insane tempo changes and a bad layout to force pages with a
variable
number of systems. You'll see that despite of this video and audio keeps
synchronized.
videohelper.ily includes some code to generate a helper file during the
lilypond run.
mkvideo is a bash script that generates videos from the lilypond output,
following
instructions generated during the lilypond run. mkvideo use the following tools
ls, sort, tail, uniq, grep, sed, bc, gs, pdftk, fluidsynth, sox, ffmpeg and the
/usr/share/sounds/sf2/FluidR3_GM.sf2 sound file.
Put the attached files into a seperate directory, don't forget "chmod 700
mkvideo",
and execute
lilypond JSBI1
./mkvideo
mkvideo is pretty fast (lilypond time here approximately 1.6 seconds, video
generation time less than 9 seconds on an i4790K system)
cu,
Knut
\version "2.11.47"
\include "articulate.ly"
% This file demonstrates how to generate sheet music and video from a single
% lilypond source file. Put it into a directory together with videohelper.ily
% and the mkvideo script, then translate it with
%
% lilypond JSBI1
% ./mkvideo
%
% mkvideo features:
%
% 1. mkvideo is fast as it runs processes in parallel if possible
%
% 2. mkvideo keeps music and audio synchronized even if there are
% tempo changes in the music and if the number of systems per
% page is not constant
%
voiceone = \relative c' {
\set Score.tempoHideNote = ##f
\tempo 4=80
r16 c[d e] f[d e c] g'8[c b^\prall c] d16[g, a b] c[a b g] d'8[g f^\prall g]
e16[a g f] e[g f a] g[f e d] c[e d f] e[d c b] a[c b d] c[b a g] fis[a g b]
\tempo 4=60
a8[d,] c'8.[^\mordent d16] b[a g fis] e[g fis a] g[b a c] b[d c e]
d[b32 c d16 g] b,8[^\prall a16 g] g8 r r4 r16 g[a b] c[a b g] fis8^\prall r
\tempo 4=40
r4 r16 a[b c] d[b c a] b8 r r4 r16 d[c b] a[c b d] c8 r r4 r16 e[d c]
\tempo 4=60
b[d cis e] d8[cis d e] f[a, b! cis] d[fis, gis a] b[c] d4 ~ d16[e, fis gis]
\tempo 4=50
a[fis gis e] e'[d c e] d[c b d] c[a' gis b] a[e f d] gis,[f' e d] c8[b16 a]
a16[a' g f] e[g f a] g2 ~ g16[e f g] a[f g e] f2 ~ f16[g f e] d[f e g]
\tempo 4=80
f2 ~ f16[d e f] g[e f d] e2 ~ e16[c d e] f[d e c] d[e f g] a[f g e] f[g a b]
c[a b g] c8[g] e[d16 c] c[bes a g] f[a g bes] a[b c e,]
\set Score.tempoHideNote = ##t
\tempo 4=65 d [ \tempo 4=50 c' \tempo 4=35 f, \tempo 4=20 b] \tempo 4=60
<c g e>1^\fermata\arpeggio \bar "|."
}
voicetwo = \relative c {
\clef "bass" r2 r16 c[d e] f[d e c] g'8[g,] r4 r16 g'[a b] c[a b g] c8[b c d]
e[g, a b] c[e, fis g] a[b] c4 ~ c16[d, e fis] g[e fis d] g8[b, c d] e[fis g e]
b8.[c16] d8[d,] r16 g[a b] c[a b g] d'8[g fis g] a16[d, e fis] g[e fis d]
a'8[d c d] g,16[\clef "treble" g' f e] d[f e g] f8[e f d] e16[a g f] e[g f a]
g8[f g e] f16[bes a g] f[a g bes] a[g f e] d[f e g] f[e d c] b[d c e] d[c b a]
gis[b a c] \clef "bass" b8[e,] d'8.[^\mordent e16] c[b a g!] fis[a gis b]
a[c b d] c[e d f] e8[a, e' e,] a8[a,] r4 r16 e''16[d c] b[d cis e]
d2 ~ d16[a b c] d[b c a] b2 ~ b16[d c b] a[c b d] c2 ~ c16[g a bes] c[a bes g]
a8[bes a g] f[d' c bes] a[f' e d] e16[d, e f] g[e f d] e8[c d e] f16[d e f]
g8[g,] <c c,>1 _\fermata \arpeggio \bar "|."
}
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%
%% PRINT version A4
%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
# ( set-global-staff-size 17.0 )
\book{
\header {
composer = "Johann Sebastian Bach (1685-1750)"
title = "Invention 1"
opus = "BWV 772"
tagline = ##f
}
\paper {
#(set-paper-size "a4")
left-margin = 1.5\cm
line-width = 18\cm
top-margin = 1.5\cm
bottom-margin = 1.5\cm
horizontal-shift = 0\mm
ragged-bottom = ##f
ragged-last-bottom = ##f
print-page-number = ##f
}
\score {
\new PianoStaff <<
\set PianoStaff.connectArpeggios = ##t
\context Staff = "one" << \voiceone >>
\context Staff = "two" << \voicetwo >>
>>
\layout{
indent = 0.0
}
}
}
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%
%% VIDEO version
%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
#(set! paper-alist (cons '("video" . (cons (* 21 cm) (* 10.5 cm))) paper-alist))
\include "videohelper.ily"
# ( set-global-staff-size 24 )
\book{
\header {
title = \markup \center-column {
\line \bold \fontsize #5 { Invention 1 } \vspace #.25
\line \bold \fontsize #2 { Johann Sebastian Bach } \vspace #.5
\line \medium \fontsize #0 { Video made with the help of Lilypond}
\line \medium \fontsize #0 { and other open source tools.} \vspace #.5
\line \medium \fontsize #0 { Demonstrates how to synchronize video and audio even in }
\line \medium \fontsize #0 { the presence of tempo changes and in case of a variable}
\line \medium \fontsize #0 { number of systems per page.}
}
tagline = ##f
}
\paper {
#(set-paper-size "video")
left-margin = 1\cm
line-width = 19.5\cm
top-margin = 1\cm
bottom-margin = 1\cm
horizontal-shift = 0\mm
ragged-bottom = ##f
ragged-last-bottom = ##f
print-page-number = ##f
max-systems-per-page = #2
}
\pageBreak
\score {
\new PianoStaff <<
\set PianoStaff.connectArpeggios = ##t
\context Staff = "one" << \voiceone >>
\context Staff = "two" << \voicetwo >>
>>
\layout{
indent = 0.0
}
}
} \pdfforvideo
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%
%% MIDI
%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
panmidi = { \set Staff.midiInstrument = #"pan flute"
\set Staff.midiMinimumVolume = #0.2
\set Staff.midiMaximumVolume = #0.2
}
pianomidi = { \set Staff.midiInstrument = #"acoustic grand"
\set Staff.midiMinimumVolume = #0.2
\set Staff.midiMaximumVolume = #0.2
}
\book{
\score {
\unfoldRepeats \articulate <<
\new PianoStaff <<
\set PianoStaff.connectArpeggios = ##t
\context Staff = "one" << {\panmidi \voiceone {r1*3 \tempo 1 = 1}} >>
\context Staff = "two" << {\panmidi \voicetwo} >>
>>
>>
\midi {
\context {
\Score
midiMinimumVolume = #0.0
midiMaximumVolume = #1.0
}
}
}
} \midiforvideo
\book{
\score {
\unfoldRepeats \articulate <<
\new PianoStaff <<
\set PianoStaff.connectArpeggios = ##t
\context Staff = "one" << {\pianomidi \voiceone {r1*3 \tempo 1 = 1}} >>
\context Staff = "two" << {\pianomidi \voicetwo} >>
>>
>>
\midi {
\context {
\Score
midiMinimumVolume = #0.0
midiMaximumVolume = #1.0
}
}
}
} \midiforvideo
%%%% This file contains code derived from Lilypond's ly/event-listener.ly.
%%%% There the following copyright notice was included:
%%%%
%%%% Copyright (C) 2011--2015 Graham Percival <[email protected]>
%%%%
%%%% Other parts of this file are based on code provide by
%%%% Thomas Morley <[email protected]>, see
%%%%
http://lilypond.1069038.n5.nabble.com/intercepting-implicit-explicit-page-breaks-td194196.html
%%%%
%%%% This 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.
%%%%
%%%% This file 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/>.
#(define out (open-output-file "videohelper.notes"))
#(define (format-moment moment)
(exact->inexact
(/ (ly:moment-main-numerator moment)
(ly:moment-main-denominator moment))))
#(define (moment-grace->string moment)
"Prints a moment without grace note(s) as a float such as
0.25000. Grace notes are written with the grace duration as a
separate \"dashed\" number, i.e. 0.25000-0.12500. This allows any
program using the output of this function to interpret grace notes
however they want (half duration, quarter duration? before beat,
after beat? etc.)."
(if
(zero? (ly:moment-grace-numerator moment))
(ly:format "~a" (format-moment moment))
;; grace notes have a negative numerator, so no "-" necessary
(ly:format
"~a~a"
(format-moment moment)
(format-moment
(ly:make-moment
(ly:moment-grace-numerator moment)
(ly:moment-grace-denominator moment))))))
#(define (make-output-string-line context values)
"Constructs a tab-separated string beginning with the
score time (derived from the context) and then adding all the
values. The string ends with a newline."
(let* ((moment (ly:context-current-moment context)))
(string-append
(string-join
(append
(list (moment-grace->string moment))
(map
(lambda (x) (ly:format "~a" x))
values))
"\t")
"\n")))
#(define (print-line context . values)
"Prints the list of values (plus the score time) to a file, and
optionally outputs to the console as well. context may be specified
as an engraver for convenience."
(if (ly:translator? context)
(set! context (ly:translator-context context)))
(display (make-output-string-line context values) out)
)
#(define (format-tempo engraver event)
(print-line engraver
"tempo"
( / 60
(* (ly:event-property event 'metronome-count)
(format-moment (ly:duration-length (ly:event-property event
'tempo-unit)))))))
#(define (format-rest engraver event)
(print-line engraver
"rest"
(format-moment (ly:duration-length
(ly:event-property event 'duration)))))
#(define (format-note engraver event)
(print-line engraver
"note"
(format-moment (ly:duration-length
(ly:event-property event 'duration)))
))
\layout {
\context {
\Voice
\consists #(make-engraver
(listeners
(tempo-change-event . format-tempo)
(rest-event . format-rest)
(note-event . format-note)))
}
}
\paper {
#(define (page-post-process layout pages)
(print-pages-first-bar-numbers layout pages #t))
}
#(define* (print-pages-first-bar-numbers layout pages #:optional print-to-file)
(let* ((lines (map (lambda (page) (ly:prob-property page 'lines)) pages))
;; list of systems of each pages
(sys
(map
(lambda (line)
(append-map
(lambda (l)
(let ((system-grob (ly:prob-property l 'system-grob)))
(if (not (null? system-grob))
(list system-grob)
system-grob))
)
line))
lines))
(sys-moment-location
(map
(lambda (m)
(if (and (not (null? m)) (ly:grob? (car m)))
(grob::when (car m))
#f))
sys))
(formatted-output
(map
(lambda (page m)
(if m
(format #f "~a page ~a\n" (format-moment m) page)
(format #f "page ~a contains no music\n" page))
)
(iota (length pages) 1 1)
sys-moment-location))
)
(if (not (null? sys-moment-location))
(begin
(for-each (lambda (i) (display i out)) formatted-output))
(for-each display formatted-output))))
#(format out "~a~a~a" "LILYSOURCE=" (ly:parser-output-name) ".ly\n")
pdfforvideo = #(define-void-function () () (format out "~a~a~a" "VIDEOSOURCE="
current-outfile-name ".pdf\n"))
midiforvideo = #(define-void-function () () (format out "~a~a~a" "MIDISOURCE="
current-outfile-name ".midi\n"))
#!/bin/bash
######################################################################
# Copyright (C) 2016 Knut Petersen ([email protected])
#
# This 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.
#
# This file 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.
######################################################################
FPS=25
TITLETIME=6.0
AFTERTIME=4.0
DEBUG=0
CLEAN=1
FAIL=0
function weneedprog {
for P in $@; do
TMP=`which $P 2> /dev/null`
if [ "x" == "x$TMP" ]; then
echo We need $P but could not find it!
FAIL=$((FAIL+1))
fi
done
}
function weneeddata {
for P in $@; do
TMP=`ls -q $P 2> /dev/null`
if [ "x" == "x$TMP" ]; then
echo We need $P but could not find it!
FAIL=$((FAIL+1))
fi
done
}
echo checking dependencies ...
weneedprog ls sort tail uniq grep sed bc gs pdftk fluidsynth sox ffmpeg
weneeddata /usr/share/sounds/sf2/FluidR3_GM.sf2 videohelper.notes
if [ $FAIL -ne 0 ]; then
echo $FAIL missing dependencies, aborting
exit 1
else
echo dependencies ok
fi
function checknotes {
grep "$1" videohelper.notes &> /dev/null
if [ $? -ne 0 ]; then
echo Fatal error: $2
exit 2
fi
}
echo checking videohelper.notes ...
checknotes "LILYSOURCE" "LILYSOURCE undefined"
checknotes "VIDEOSOURCE" "VIDEOSOURCE undefined"
checknotes "MIDISOURCE" "MIDISOURCE undefined"
checknotes "tempo" "no tempo definition"
checknotes "note" "not a single note event"
checknotes "page 1 contains no music" "no title page defined"
checknotes "[0-9.]* page" "not a single page"
echo videohelper.notes ok
eval `grep LILYSOURCE videohelper.notes`
eval `grep VIDEOSOURCE videohelper.notes`
MIDILIST=`grep MIDISOURCE videohelper.notes | sed -e
"s/MIDISOURCE=\([[:print:]]*\).midi/\1/" | sort | uniq | sed ':a;N;$!ba;s/\n/
/g'`
LASTMOMENT=$(echo `sort -n videohelper.notes | grep 'note\|rest' | tail -n 1 | \
sed -e
's/\([0-9.]*\)\([[:space:]]*\)\(note\|rest\)\([[:space:]]*\)\([0-9.]*\)/\1+\5/'`
| bc -l)
MOMENTLIST="`grep "[0-9.]* page" videohelper.notes | sed -e
"s/\([0-9.]*\)[[:space:]][[:print:]]*/\1/" | sed ':a;N;$!ba;s/\n/ /g'`
$LASTMOMENT"
COUNT=1
ARMOM=()
for VAL in $MOMENTLIST; do
ARMOM[COUNT]=$VAL
COUNT=$((COUNT+1))
done
if [ $DEBUG -ne 0 ] ; then
declare -p ARMOM
fi
TEMPOMOMENTLIST=`grep tempo videohelper.notes | sed -e
"s/\([0-9.]*\)[[:space:]]tempo[[:space:]]\([0-9.]*\)/\1 /" | sed
':a;N;$!ba;s/\n/ /g'`
COUNT=1
TMMOM=()
for VAL in $TEMPOMOMENTLIST; do
TMMOM[COUNT]=$VAL
COUNT=$((COUNT+1))
done
TMMOM[COUNT]=100000.0
if [ $DEBUG -ne 0 ] ; then
declare -p TMMOM
fi
TEMPOTIMELIST=`grep tempo videohelper.notes | sed -e
"s/\([0-9.]*\)[[:space:]]tempo[[:space:]]\([0-9.]*\)/\2 /" | sed
':a;N;$!ba;s/\n/ /g'`
COUNT=1
TTMOM=()
for VAL in $TEMPOTIMELIST; do
TTMOM[COUNT]=$VAL
COUNT=$((COUNT+1))
done
TTMOM[COUNT]=0.0
if [ $DEBUG -ne 0 ] ; then
declare -p TTMOM
fi
TEMPOS=`grep tempo videohelper.notes | wc | sed -e
"s/[[:space:]]*\([[:digit:]]*\)[[:print:]]*/\1/"`
if [ $DEBUG -ne 0 ] ; then
echo TEMPOS $TEMPOS
fi
PAGES=`grep page videohelper.notes | wc | sed -e
"s/[[:space:]]*\([[:digit:]]*\)[[:print:]]*/\1/"`
PAGES=$((PAGES-1))
if [ $DEBUG -ne 0 ] ; then
echo PAGES $PAGES
fi
MOMENTS=$((PAGES+1))
if [ $DEBUG -ne 0 ] ; then
echo MOMENTS $MOMENTS
fi
for M in `seq 1 $MOMENTS`;
do ARTIMES[M]=0.0
done
for T in `seq 1 $TEMPOS`; do
for M in `seq 1 $MOMENTS`; do
if (( $(echo "${ARMOM[$M]} > ${TMMOM[$T]}" |bc -l) )); then
if (( $(echo "${ARMOM[$M]} <= ${TMMOM[$((T+1))]}" |bc -l) )); then
ARTIMES[$M]=`bc -l
<<<"${ARTIMES[$M]}+(${ARMOM[$M]}-${TMMOM[$T]})*${TTMOM[$T]}"`
else
ARTIMES[$M]=`bc -l
<<<"${ARTIMES[$M]}+(${TMMOM[$((T+1))]}-${TMMOM[$T]})*${TTMOM[$T]}"`
fi
fi
done
done
for M in `seq 1 $MOMENTS`; do
ARTIMES[$M]=$(echo $(bc <<< "(${ARTIMES[$M]})*$FPS/1")/$FPS | bc -l)
done
if [ $DEBUG -ne 0 ] ; then
echo The first page \(title\) will be visible for "$TITLETIME"s.
echo The last page will be visible for "$AFTERTIME"s after the end of the
last note/rest.
for P in `seq 1 $PAGES`; do
echo "page $((P+1)) from moment ${ARMOM[$P]} to ${ARMOM[$((P+1))]}
(${ARTIMES[$P]}s to ${ARTIMES[$((P+1))]}s)"
done
fi
ARVT[1]=$TITLETIME
for M in `seq 1 $PAGES`;
do
ARVT[$((M+1))]=`bc <<<${ARTIMES[$((M+1))]}-${ARTIMES[$M]}`
done
ARVT[$MOMENTS]=`bc <<<${ARVT[$MOMENTS]}+$AFTERTIME`
if [ $DEBUG -ne 0 ] ; then
declare -p ARVT
fi
PAGELIST=`grep -o "page [[:digit:]]*" videohelper.notes | sort -n | sed -e
"s/\(page\) \([[:digit:]]*\)/\1\2/" | sed ':a;N;$!ba;s/\n/ /g'`
echo generating tsilence.wav ...
sox -n -r 44100 -c 2 -b16 tsilence.wav trim 0.0 $TITLETIME &>/dev/null &
echo generating wav files from midi input ...
for M in $MIDILIST;
do
fluidsynth -ln --fast-render=$M-tmp1.wav
/usr/share/sounds/sf2/FluidR3_GM.sf2 $M.midi &>/dev/null &
done
echo bursting pdf ...
pdftk $VIDEOSOURCE burst output page%d.pdf
for P in `seq 1 $MOMENTS`;
do
echo generating page$((P)).h264, length: ${ARVT[$P]}s
gs -dBATCH -dNOPAUSE -q -r495.421 -sDEVICE=pnggray -sOutputFile=-
page$((P)).pdf | \
ffmpeg -y -framerate 1/100000 -i - \
-vf scale=1024:512 -c:v libx264 -tune stillimage -preset ultrafast \
-pix_fmt yuv420p -r 25 -t ${ARVT[$P]} page$((P)).h264 &> /dev/null &
done
echo synchronizing ...
wait
echo normalizing audio data ...
for M in $MIDILIST;
do
sox -v `sox $M-tmp1.wav -n stat -v 2>&1` $M-tmp1.wav $M-tmp2.wav &>/dev/null &
done
echo synchronizing ...
wait
echo adding silence to audio data ...
for M in $MIDILIST;
do
sox tsilence.wav $M-tmp2.wav $M.wav &>/dev/null &
done
echo synchronizing ...
wait
COUNT=0
for M in $MIDILIST;
do
echo generating $M.mp4 ...
if [ $COUNT -eq 0 ]; then
grep -o "page [[:digit:]]*" videohelper.notes | sort -n -k 2 | sed -e
"s/\(page\) \([[:digit:]]*\)/file \1\2.h264/" >concat.txt
ffmpeg -y -f concat -i concat.txt -i $M.wav -c:v libx264 -tune stillimage \
-preset ultrafast -pix_fmt yuv420p -r 25 -shortest $M.mp4
&>/dev/null
COUNT=$((COUNT+1))
FIRSTVIDEO=$M.mp4
else
ffmpeg -y -i $FIRSTVIDEO -i $M.wav -c:v copy -map 0:v:0 -map 1:a:0
-shortest $M.mp4 &>/dev/null &
COUNT=$((COUNT+1))
fi
done
echo synchronizing ...
wait
echo removing temporary files ...
if [ $CLEAN -eq 1 ]; then
for X in $PAGELIST; do
rm $X.pdf $X.h264
done
for X in $MIDILIST; do
rm $X-tmp1.wav $X-tmp2.wav $X.wav
done
rm concat.txt doc_data.txt tsilence.wav
fi
_______________________________________________
lilypond-user mailing list
[email protected]
https://lists.gnu.org/mailman/listinfo/lilypond-user