Extension:AbcMusic
From MediaWiki.org
|
Release status: beta |
|
|---|---|
| Implementation | Tag |
| Description | Rendering of music from ABC notation |
| Author(s) | David A. Tanzer (David A. TanzerTalk) |
| Last Version | 1.0 (2008-05-28) |
| MediaWiki | 1.6+ |
| License | No license specified |
| Download | Download the latest version |
|
check usage (experimental) |
|
Contents |
[edit] Overview
You can typeset music with this extension, using the ABC notation.
Use the tags <abc>...</abc> to enclose ABC text.
Use the tags <abclink>...</abclink> to enclose a URL to an ABC file.
See the Wikipedia article on ABC notation for links to tutorials and song collections.
Here is an international music wiki that uses the extension. It has special emphasis on fundamental rhythms.
[edit] Example: Inline ABC
Source:
<abc> X:1 T: Twinkle Twinkle Little Star M: 2/4 L: 1/4 K: C |"C" C C | "C" G G | "F" A A | "C" G2 | w: Twin- kle twin- kle lit- tle star, "F" F F | "C" E E | "G7" D D | "C" C2 | w: how I won- der what you are. </abc>
[edit] Example: Links to External ABC Files
Source:
<abclink> http://ecf-guest.mit.edu/~jc/music/abc/Russia/Hopak.abc </abclink>
This is a link to a file in John Chamber's online ABC music collection.
[edit] Implementation Notes
This implementation uses LilyPond as the underlying rendering engine. LilyPond comes packaged with a script, abc2ly, that converts ABC notation to LilyPond. The extension calls abc2ly then LilyPond.
The script abc2ly is a very nice piece of code, but it is not being actively developed, and it is lagging behind the current ABC standard. (Programmers, go forth and develop!)
abc2ly does not handle multiple songs in a single file, so this extension splits a multi-song file into separate parts, and makes repeated calls to abc2ly then LilyPond. This is suboptimal, in terms of process overhead.
This extension will inherit any other limitations of abc2ly.
[edit] Further Work
1. Make the generated MIDI file available. Just follow the coding pattern used in Extension:LilyPond for exposing the midi file.
2. Include an enhanced script abc2ly as part of this extension.
3. Tag to include a directory of .abc files on the web.
4. Tag to crawl and render a tree of .abc files on the web.
[edit] Acknowledgement
This code was originally derived from Extension:LilyPond.
[edit] Installation
Make sure you have LilyPond installed.
Then copy AbcMusic.php into the extensions/ directory.
[edit] Changes to LocalSettings.php
require_once("$IP/extensions/AbcMusic.php");
[edit] Code
<?php /** * AbcMusic.php -- MediaWiki Extension * * @author David A. Tanzer * @link http://www.mediawiki.org/wiki/Extension:AbcMusic */ $wgLilypond = "/usr/bin/lilypond"; $wgAbc2ly = "/usr/bin/abc2ly"; $wgExtensionCredits['parserhook'][] = array( 'name' => 'AbcMusic', 'version' => '1.0', 'author' => 'David A. Tanzer', 'url' => 'http://www.mediawiki.org/wiki/Extension:AbcMusic', 'description' => 'Rendering of music from the ABC text notation.', ); $wgExtensionFunctions[] = 'wfAbcMusicExtension'; function wfAbcMusicExtension() { global $wgParser; $wgParser->setHook( 'abc', 'renderAbc' ); $wgParser->setHook( 'abclink', 'renderAbcLink' ); } function renderAbcLink( $abcLinkText ) { return renderAbc(file_get_contents(trim($abcLinkText))); } function renderAbc( $abcText ) { $result = ""; $songs = splitSongs( $abcText ); foreach ($songs as $song) { $result = $result . renderSong( $song ); } return $result; } // split the text into a list of substrings, one for each song within // the file. Every song in an ABC file begins with an X: line. // function splitSongs( $abcText ) { $songs = array(); $lines = explode( "\n", $abcText ); $firstSong = 1; foreach ($lines as $line) { $line = trim( $line ); if ($line[0] == "X" && $line[1] == ":") { // first line of a new song if ( $firstSong != 1 ) { // append the previous song to the result array $songs[] = implode( "\n", $currSong ); } $firstSong = 0; $currSong = array(); } $currSong[] = $line; } $songs[] = implode( "\n", $currSong ); return $songs; } function renderSong( $abcText ) { global $wgMathPath, $wgMathDirectory, $wgTmpDirectory, $wgLilypond, $wgAbc2ly; $mf = wfMsg( "math_failure" ); // filename is based upon a large hash of the entire file contents // $hash = md5( $abcText ); $link = "<img src=\"" . $wgMathPath . "/" . $hash.".png\" alt=\"" .htmlspecialchars( $abcText )."\">"; // if the output file already exists, just return the html // that points to it. // if( file_exists( "$wgMathDirectory/$hash.png" ) ) { return $link; } $dirMsg = ensureDirectories(); if ($dirMsg != "") { return $dirMsg; } // write the string to a temporary .abc file // $abcFile = $hash . ".abc"; $abcOut = fopen( $wgTmpDirectory . "/" . $abcFile, "w"); fwrite( $abcOut, $abcText ); fclose( $abcOut ); $lyFile = $hash . ".ly"; $lyFile2 = "sed_" . $lyFile; wfDebug( "Lilypond: $cmd" ); $cmdAbc = $wgAbc2ly . " " . escapeshellarg($abcFile) . " 2>&1"; // some dirty-work: use sed to remove the tagline from the generated .ly file // the current version of abc2ly lacks a command-line argument to modify, or suppress, the tagline // $sedArgFname = $wgTmpDirectory . "/sedargfile"; $sedArgOut = fopen( $sedArgFname, "w" ); fwrite( $sedArgOut, "s/tagline = .*/tagline = \"\"/" ); fclose( $sedArgOut ); $cmdSed = "sed -f " . $sedArgFname . " < ". $lyFile . " > " . $lyFile2; $cmdLily = $wgLilypond . " --safe --backend=eps --format=png --header=texidoc " . escapeshellarg($lyFile2) . " 2>&1"; $oldcwd = getcwd(); chdir( $wgTmpDirectory ); exec( $cmdAbc, $output, $ret ); // abc -> ly exec( $cmdSed, $output, $ret ); // remove tagline from .ly exec( $cmdLily, $output, $ret ); // ly -> png chdir( $oldcwd ); if( $ret != 0 ) { return "<br><b>LilyPond error:</b><br><i>" . str_replace( array( $hash, " " ), array( "<b>your code</b>", " " ), nl2br( htmlentities( join( "\n", $output ) ) ) ) . "</i><br>"; } $outputFile = $wgTmpDirectory."/sed_".$hash.".png"; if( !file_exists( $outputFile ) ) { return "<b>$mf (" . wfMsg( "math_image_error" ) . ")</b>"; } rename( $outputFile, $wgMathDirectory."/".$hash.".png" ); unlink( $sedArgFname ); removeTempFiles($hash); if( !file_exists( $wgMathDirectory."/".$hash.".png" ) ) { $errmsg = wfMsg( "math_image_error" ); return "<h3>$mf ($errmsg): " . htmlspecialchars($abcText) . "</h3>"; } return $link; } function ensureDirectories() { global $wgMathDirectory, $wgTmpDirectory; 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>"; } return ""; } function removeTempFiles($hash) { global $wgTmpDirectory, $wgMathDirectory; $files = opendir( $wgTmpDirectory ); $last_page = 0; while( false !== ($file = readdir( $files ))) { if( substr( $file, 4, 32 ) != $hash && substr( $file, 0, 32 ) != $hash ) { continue; } $file_absolute = $wgTmpDirectory . "/" . $file; if( !is_file( $file_absolute ) ) continue; unlink( $file_absolute ); } closedir( $files ); } ?>

