Home Updates Messages SandBox

Music Wiki

A wiki that would enable the users to discuss about music, exchange music-related ideas, etc.

At the minimum, it would have to allow typesetting music sheets, possibly with some external program like Lilypond. Generating MIDI files for easy listening and importing into various programs and devices would be nice too.

This module is one way to do it:

# This program 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 2 of the License, or
# (at your option) any later version.
#
# This program 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 this program; if not, write to the
#    Free Software Foundation, Inc.
#    59 Temple Place, Suite 330
#    Boston, MA 02111-1307 USA

use Digest::MD5 qw(md5 md5_hex md5_base64);

use vars qw($MusicDir $MusicLinkDir $MusicExtendPath);

$ModulesDescription .= '<p>Music</p>';

$MusicExtendPath = ':/usr/bin:/bin:/usr/local/bin';

$MusicDir    = "$DataDir/../html/music";
$MusicLinkDir= "/static/music";

push(@MyRules, \&MusicRule);

sub MusicRule {
  if (m/\G&lt;music(.*?)&gt;(.*?)&lt;\/music&gt;/gcms) {
    return &MakeMusic($2,$1);
  }
  return undef;
}

sub MakeMusic {
  my ($music, $ops) = @_;
  $ENV{PATH} .= $MusicExtendPath
    if $MusicExtendPath and $ENV{PATH} !~ /$MusicExtendPath/;
  $music = UnquoteHtml($music); # Change &lt; back to <, for example
  my $hash = md5_hex($music);
  $hash =~ s/%//g;
  # check cache
  if (-f "$MusicDir/$hash.png" and not -z "$MusicDir/$hash.png") {
    if (-f "$MusicDir/$hash.midi" and not -z "$MusicDir/$hash.midi") {
        return ("<a href=\"$MusicLinkDir/$hash.midi\" class=\"music\"><img class=\"music\" src=\"$MusicLinkDir/$hash.png\" alt=\"Music\"$ops></a>");
    }
    return ("<img class=\"music\" src=\"$MusicLinkDir/$hash.png\" alt=\"Music\"$ops>");
  }
  mkdir($MusicDir) unless -d $MusicDir;

  #setup rendering directory
  my $dir = "$MusicDir/$hash";
  if (-d $dir) {
    unlink (glob('$dir/*'));
  } else {
    mkdir($dir) or return "[Unable to create $dir]";
  }
  chdir ($dir) or return "[Unable to switch to $dir]";
  WriteStringToFile ("music.ly", $music);
  qx( lilypond --safe --png --output=out music.ly );
  return "[Lylypond error: $!]" if $!;
  qx( montage out-page*.png -gravity North -geometry 760x+0 -tile 1x+0 out.png );
  return "[Montage error: $!]" if $!;
  my $result;
  if (-f 'out.png' and not -z 'out.png') {
    my $png = ReadFileOrDie("out.png");
    WriteStringToFile ("$MusicDir/$hash.png", $png);
    chmod 0755, "$MusicDir/$hash.png";
    $result = "<img class=\"music\" src=\"$MusicLinkDir/$hash.png\" alt=\"music\">";
  } else {
    $result = "[Error retrieving music]";
  }
  if (-f 'out.midi' and not -z 'out.midi') {
    my $png = ReadFileOrDie("out.midi");
    WriteStringToFile ("$MusicDir/$hash.midi", $png);
    chmod 0755, "$MusicDir/$hash.midi";
    $result = "<a class=\"music\" href=\"$MusicLinkDir/$hash.midi\">".$result."</a>";
  }
  unlink (glob('*'));
  chdir ($MusicDir);
  rmdir ($dir);
  return $result;
}

Example of the module in action:

<music>
\header {
  title = "Rock the Casbah"
  subtitle = "Don't let it rock you"
  source = ""
  composer = "Osho (Never born, never died)"
  enteredby = "jcn"
  copyright = "Copyright (c) the Universe"
}

\version "2.10.0"

global =  {
  \key a \minor
  \time 2/4
  s2*10
  \bar "||"
  s2*11
  \bar "||"
  \time 3/4
  s2.
  \bar "||"
  \time 2/4
  s2*4
  \bar "||"
  \time 3/4
  s2.*2
  \bar "||"
  \time 2/4
  s2*18
  \bar "|."
}

i = \context Staff \relative c''\new Voice {
  \voiceOne

  c8.( es16 bes4 ~ |  bes8) r c8.( bes16 | des4 c8. bes16 | c4 ~  c8) r |
  c4( f,8. as16 | bes4 ~  bes8) r | f8.( es16 f4 | es  f) |
  g8.( es16 f4 ~ |  f) f8 r

  % Au mouvement
  f4 ( g | a2 ~ |  a) | a4-- a-> ~ | a8 r b!8.( a16 |  b4) c-- ~ |
  c8 r b8.( d16 | a4 ~  a8) r | d4( cis | c! b8. d16 | a4 ~  a8) r

  a8.( g16 a4 ~  a8) r |

  a4-> ~ a8 r | g8.( a16  fis4) | e8.( d16 e4 | fis ~  fis8) r

  d4( d-- ~  d8) r e4( f!2 ~ |

  f4 ~  f8) r | es4( g | as bes ~ |  bes) c( |  b!2) | c4( d |  bes2) | c4~ c8 r |

  % copy from begin: 1-10
  c8.( es16 bes4 ~ |  bes8) r c8.( bes16 | des4 c8. bes16 | c4 ~  c8) r |
  c4( f,8. as16 | bes4 ~  bes8) r | f8.( es16 f4 | es  f) |
%  g8.( es16 f4 ~ |  f) f8 r ?
  g8.( es16 f4 ~ |  f) ~ f8 r

}

ii = \context Staff \relative c'\new Voice{
  \voiceTwo

  r8 <es as> r <des f> | r <es g> r <es as> | r <f as> r <f as> |
  r <es g> r <es g> | r <es as> r <as, des> | r <des f> r <des f> |
  r <as des> r <bes des> | r bes r <as c> | r <g des'> r <bes d> |
  r <a! c> r <a c>

  % Au movement
  r <as des> r <c e> | r <c f> r <d! f> | r <cis e> r <cis e> |
  r <c! f> r <d f> | r <d f> r <d g> | r <e g> r <e g> | r <e g> r <d g> |
  r <d fis> r <d fis> | r <fis b> r <fis a> | r <e a> r <d g> |
  r <d fis> r <d fis> |

  r <cis e> r <cis fis> r <cis f> |

  r <cis e> r <cis e> | r <b d> r <a d> | r <g b> r <a cis> |
  r <a cis> r <a cis> |

  \change Staff=bass\voiceOne
  r <g b> r <fis a> r <fis a> | r <g bes>
  \change Staff=treble\voiceTwo
  r <a c> r <a d> |

  r <bes d> r <bes d> | r <g c> r <bes d> | r <c es> r <d g> |
  r <d f> r <es g> | r <e! g> r <d fis> | r <e a> r <fis a> |
  r <es g> r <es g> | r <es g> r <es g> |


  % copy from begin: 1-10
  r8 <es as> r <des f> | r <es g> r <es as> | r <f as> r <f as> |
  r <es g> r <es g> | r <es as> r <as, des> | r <des f> r <des f> |
  r <as des> r <bes des> | r bes r <as c> | r <g des'> r <bes d> |
  r <a! c> r <a c>

}

lower = \context Staff  \relative c \new Voice{

  <as as'>4 <es es'> | r <as as'> | <des, des'> <f f'> | <c c'> r |
  <as as'> <des des'> | <bes bes'> r | <des des'> <bes bes'> |
  <g g'> <f f'> | <c' c'> <bes bes'> | <f f'> r |

  % Au movement
  <des' des'> <c c'> | <f f'> <d! d'!> | <a a'> r | <f' f'> <d d'> |
  r <f f'> | <e e'> <c c'> | r <g g'> | <d' d'> r | <b b'> <fis fis'> |
  <a a'> <b b'> | <d d'> r |

  <a a'> <fis fis'> r |

  <a a'> r | <b b'> <d d'> | <e e'> <a, a'> | <fis fis'> r |

  <g g'> <d d'> r | <g g'> <f! f'!> <d' d'> |

  <bes bes'> r | <c c'>  <bes bes'> | <as as'> <g g'> | <d' d'> <c c'> |
  <e! e'!> <b! b'!> | <a a'> <d d'> | <es es> r | <c c'> r

  % copy from begin: 1-10
  <as' as'>4 <es es'> | r <as as'> | <des, des'> <f f'> | <c c'> r |
  <as as'> <des des'> | <bes bes'> r | <des des'> <bes bes'> |
  <g g'> <f f'> | <c' c'> <bes bes'> | <f f'> r |

}

\score {
    \context PianoStaff <<
	\new Staff = "treble" <<
	    \global
	    \clef violin
	    \i
	    \ii
	>>
	\new Staff = "bass" <<
	    \global
	    \clef bass
	    \lower
	>>
    >>
    \layout {
	\context {
	    \Score
	    \override SpacingSpanner #'spacing-increment = #3
	}
    }

  \midi {
    \context {
      \Score
      tempoWholesPerMinute = #(ly:make-moment 60 4)
      }
    }


}

%% Local Variables:
%% coding: utf-8
%% End:
</music>