Extension:LilyPond
From MediaWiki.org
|
Release status: beta |
|
|---|---|
| Implementation | Tag |
| Description | This extension adds the option to typeset music |
| Author(s) | Johannes E. Schindelin |
| MediaWiki | 1.6+ |
| License | No license specified |
| Download | See Extension:LilyPond for latest version |
|
check usage (experimental) |
|
Contents |
[edit] What can this extension do?
You can typeset music with this extension.
[edit] Usage
Just enclose some music in LilyPond syntax in <lilypond>...</lilypond> tags. For example, this code:
<lilypond>f, d f a d f e d cis a cis e a g f e</lilypond>
will yield this image:
If you enclose the music in <lilymidi>...</lilymidi> tags, the image will be clickable, and the link will download the MIDI file.
Another option is to use <lilybook>...</lilybook> tags, which creates a full page of music. for example, this code:
<lilybook>
\version "2.10.29"
\header {
title = "Mary Had a Little Lamb"
tagline = ""
}
\paper {
ragged-right = ##t
ragged-bottom = ##t
indent = 0\mm
}
melody = \relative c' {
e d c d | e e e e |
d d e d | c1 |
}
text = \lyricmode {
\set stanza = "1." Ma- ry had a lit- tle lamb,
its fleece was white as snow.
}
\book{
\score{ <<
\new Voice = "one" { \melody }
\new Lyrics \lyricsto "one" \text
>>
\layout { }
\midi { }
}
\markup{
\wordwrap-string #"
Verse 2.
All the children laughed and played,
To see a lamb at school."
}
}
</lilybook>
will yield this image:
[edit] Installation
Make sure you have LilyPond installed.
Then copy LilyPond.php into the extensions/ directory.
[edit] Parameters
None.
See optional user setting in code.
[edit] Changes to LocalSettings.php
require_once("$IP/extensions/LilyPond.php");
[edit] Code
<?php /* MediaWiki extension: LilyPond ============================= To activate, edit your LocalSettings.php, add require_once("$IP/extensions/LilyPond.php"); and make sure that the images/ directory is writable. Example wiki code: <lilypond>c d e f g</lilypond> If you want to typeset a fragment with clickable midi, use <lilymidi>...</lilymidi> If you want write a complete lilypond file, use <lilybook>...</lilybook> Tested with Lilypond version 2.10.29. */ # User Settings # Add a text link to prompt user to listen to midi, remember line breaks $wgLilypondPreMidi = ""; # eg " Listen<br>" $wgLilypondPostMidi = ""; # eg " <br>Listen" # You can set the global variable $wgLilypond if you want to override the # path to the Lilypond executable. # $wgLilypond = "/home/username/bin/lilypond"; # End User Settings $wgExtensionFunctions[] = "wfLilyPondExtension"; function wfLilyPondExtension() { global $wgParser; $wgParser->setHook( "lilypond", "renderLilyPondFragment" ); $wgParser->setHook( "lilymidi", "renderLilyPondMidiFragment" ); $wgParser->setHook( "lilybook", "renderLilyPond" ); } if( !isset( $wgLilypond ) ) $wgLilypond = "PATH=\$PATH:/usr/local/bin /usr/local/bin/lilypond"; function renderLilyPondMidiFragment( $lilypond_code ) { return renderLilyPondFragment( $lilypond_code, true ); } function renderLilyPondFragment( $lilypond_code, $midi=false ) { return renderLilyPond("\\header {\n" . "\ttagline = \"\"\n" . "}\n" . "\\paper {\n" . "\traggedright = ##t\n" . "\traggedbottom = ##t\n" . "\tindent = 0\mm\n" . "}\n" . "\\score {\n" . "\t\\relative c'' {\n" . $lilypond_code . "\t}\n" . "\t\\layout { }\n" . ($midi?"\t\\midi { }\n":"") . "}\n", $lilypond_code ); } function renderLilyPond( $lilypond_code, $short_code=false ) { global $wgMathPath, $wgMathDirectory, $wgTmpDirectory, $wgLilypond, $wgLilypondPreMidi, $wgLilypondPostMidi; $mf = wfMsg( "math_failure" ); $munk = wfMsg( "math_unknown_error" ); $fname = "renderMusic"; $md5 = md5($lilypond_code); if( file_exists( $wgMathDirectory."/".$md5.".midi" ) ) { $pre = "<a href=\"".$wgMathPath."/".$md5.".midi\"> " . $wgLilypondPreMidi; $post = $wgLilypondPostMidi . " </a>"; } else { $pre = ""; $post = ""; } # if short_code is supplied, this is a fragment if( $short_code ) { $link = "<img src=\"".$wgMathPath."/".$md5.".png\" alt=\"" .htmlspecialchars( $short_code )."\">"; if( file_exists( "$wgMathDirectory/$md5.png" ) ) { return $pre.$link.$post; } } else { if( file_exists( "$wgMathDirectory/$md5-1.png" ) ) { $link=""; for($i=1; file_exists( $wgMathDirectory . "/" . $md5 . "-" . $i . ".png" ); $i++) { $link .= "<img src=\"" . $wgMathPath . "/" . $md5 . "-" . $i . ".png\" alt=\"" . htmlspecialchars( "page ".$i )."\">"; } return $pre.$link.$post; } } # Ensure that the temp and output dirs are available before continuing. if( !file_exists( $wgMathDirectory ) ) { if( !@mkdir( $wgMathDirectory ) ) { return "<b>$mf (" . wfMsg( "math_bad_output" ) . $wgMathDirectory . ")</b>"; } } elseif( !is_dir( $wgMathDirectory ) || !is_writable( $wgMathDirectory ) ) { return "<b>$mf (" . wfMsg( "math_bad_output" ) . ")</b>"; } if( !file_exists( $wgTmpDirectory ) ) { if( !@mkdir( $wgTmpDirectory ) ) { return "<b>$mf (" . wfMsg( "math_bad_tmpdir" ) . ")</b>"; } } elseif( !is_dir( $wgTmpDirectory ) || !is_writable( $wgTmpDirectory ) ) { return "<b>$mf (" . wfMsg( "math_bad_tmpdir" ) . ")</b>"; } $lyFile = $md5.".ly"; $out = fopen( $wgTmpDirectory."/".$lyFile, "w" ); if( $out === false ) { return "<b>$mf (" . wfMsg( "math_bad_tmpdir" ) . ")</b>"; } fwrite( $out, $lilypond_code ); fclose( $out ); $cmd = $wgLilypond . " -dsafe='#t' -dbackend=eps --png --header=texidoc " . escapeshellarg($lyFile) . " 2>&1"; wfDebug( "Lilypond: $cmd" ); $oldcwd = getcwd(); chdir( $wgTmpDirectory ); $contents = exec( $cmd, $output, $ret ); chdir( $oldcwd ); if( $ret != 0 ) { return "<br><b>LilyPond error:</b><br><i>" . str_replace( array( $md5, " " ), array( "<b>your code</b>", " " ), nl2br( htmlentities( join( "\n", $output ) ) ) ) . "</i><br>"; } if($short_code) { $outputFile = $wgTmpDirectory."/".$md5.".png"; if( !file_exists( $outputFile ) ) { return "<b>$mf (" . wfMsg( "math_image_error" ) . ")</b>"; } rename( $outputFile, $wgMathDirectory."/".$md5.".png"); } # remove all temporary files $files = opendir( $wgTmpDirectory ); $last_page = 0; while( false !== ($file = readdir( $files ))) { if( substr( $file, 0, 32 ) != $md5 ) continue; $file_absolute = $wgTmpDirectory . "/" . $file; if( !$short_code && preg_match( '/-page(\d+)\.png$/', $file, $matches ) ) { if($matches[1]>$last_page) $last_page = $matches[1]; rename( $file_absolute, $wgMathDirectory . "/" . $md5 . "-" . $matches[1] . ".png" ); continue; } if( preg_match( '/.png$/', $file ) ) { rename( $file_absolute, $wgMathDirectory."/".$md5.".png" ); continue; } if( preg_match( '/.midi$/', $file ) ) { rename( $file_absolute, $wgMathDirectory . "/" . $md5 . ".midi" ); $pre = "<a href=\"".$wgMathPath."/".$md5.".midi\"> " . $wgLilypondPreMidi; $post = $wgLilypondPostMidi . " </a>"; continue; } if( !is_file( $file_absolute ) ) continue; unlink( $file_absolute ); } closedir( $files ); if( $short_code ) { if( !file_exists( $wgMathDirectory."/".$md5.".png" ) ) { $errmsg = wfMsg( "math_image_error" ); return "<h3>$mf ($errmsg): " . htmlspecialchars($lilypond_code) . "</h3>"; } } else { $link.="<img src=\"".$wgMathPath."/".$md5.".png\" alt=\"" .htmlspecialchars( "page " )."\">"; } return $pre . $link . $post; }
[edit] Trimming lilybook Images to an Appropriate Size
Users have reported that the "lilybook" tag produces page-sized images (too much whitespace), no matter what the tagline in the header is. A solution, which requires the GD library (bundled with PHP since PHP version 4.3.0, although an obsolescent version is available externally), is detailed below. The code was produced by Dan Williams and Vanessa X.
First, add the following immediately before the last return statement in the code above:
trimImage($wgMathDirectory ."/" .$md5 . ".png", "png", $wgMathDirectory ."/" .$md5 . ".png", 20, 20);
Then append the following function to the end of the code:
function trimImage($source, $stype, $dest, $border_x, $border_y) { $size = getimagesize($source); $w = $size[0]; $h = $size[1]; $simg = imagecreatefrompng($source); $min_color_found = 100000000; $max_color_found = 0; for ( $qi = 0; $qi < $w; $qi++){ for ( $qj = 0; $qj < $h; $qj++){ $rgb = imagecolorat($simg, $qi , $qj); if ($rgb < $min_color_found) { $min_color_found = $rgb; } if ($rgb > $max_color_found) { $max_color_found = $rgb; } } } $min_x = $w + 1; $max_x = -1; $min_y = $h + 1; $max_y = -1; for ( $qi = 0; $qi < $w; $qi++){ for ( $qj = 0; $qj < $h; $qj++){ $rgb = imagecolorat($simg, $qi , $qj); if ($rgb == $min_color_found) { if ($qi < $min_x) { $min_x = $qi; } if ($qi > $max_x) { $max_x = $qi; } if ($qj < $min_y) { $min_y = $qj; } if ($qj > $max_y) { $max_y = $qj; } } } } $dimg = imagecreatetruecolor($max_x - $min_x + 2 * $border_x, $max_y - $min_y + 2 * $border_y); imagefill($dimg, 0, 0, $max_color_found); imagecopy($dimg, $simg, $border_x, $border_y, $min_x, $min_y, $max_x - $min_x + 1, $max_y - $min_y + 1); imagepng($dimg, $dest); }

