. Changed history link to point to github.
. On the song page, the heading is now fixed to the top part of the page.
Included in the heading is the home button, the title of the song and
artist (not linked to show all of the artist's songs) and the HTML5 Audio
player.
. The bottom part contains the song itself as before. I may add an auto
scroll option later. It'd be really cool if I could tag the various lines
and chorus such that the playback syncs with the words but I'm not sure I
can do that.
. I've added JavaScript to allow the space bar to play/pause playback.
. Added additional keyboard shortcuts for:
. r - return to start (initially start is 0 but can be set by the "a"
command)
. b - rewind back 10 seconds. I choose 10 because that seemed to be a
good amount to rew/ff. Hit b or f multiple times to go forward or
backward by 10 secs increments.
. f - Fast forward 10 seconds.
. a - Mark start. Say you are listening and playing/singing along with a
song and you want to practice that part just before the second
chorus. Well mark that spot with the "a" key. Then play/sing along.
Didn't get it right? Hit either "r" or the space bar to start at the
marked position again.
. c - Clear start. If you don't want that start position anymore clear it
with "c". Then "r" or space bar will start from 0.
. ? - Hit "?" to see the keyboard short cuts.
<tbody>
<tr>
<td align="left"><a href="news.html"><img src="/Icons/news.png"></a></td>
- <td align="right"><a href="/gitweb/?p=songbook.git"><img src="/Icons/history.png"></a></td>
+ <td align="right"><a href="https://github.com/adefaria/songbook"><img src="/Icons/history.png"></a></td>
</tr>
</tbody>
</table>
--- /dev/null
+.help-underlay * {
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+}
+
+.help-underlay {
+ position: absolute;
+ background: #555;
+ background: rgba(0,0,0,0.5);
+ visibility: hidden;
+ opacity: 0;
+ -webkit-transition: opacity .2s linear;
+ -moz-transition: opacity .2s linear;
+ -o-transition: opacity .2s linear;
+ transition: opacity .2s linear;
+ left: 0; right: 0; top: 0; bottom: 0;
+}
+
+.ie8 .help-underlay {
+ -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(opacity=95)"; /* for IE8 in IE7 mode */
+ filter: alpha(opacity=95); /* for IE8 */
+ visibility: hidden;
+}
+
+.help-modal {
+ position: fixed;
+ z-index: 99999;
+ left: 0; right: 0; top: 0; bottom: 0;
+ width: 80%;
+ margin: auto;
+ background: #fff;
+ color: #676767;
+ max-height: 80%;
+ overflow: auto;
+ font-family: Arial, sans-serif;
+ -webkit-border-radius: 3px;
+ -moz-border-radius: 3px;
+ border-radius: 3px;
+ -webkit-box-shadow: 0 4px 12px rgba(0,0,0,.4), inset 0 1px 0 rgba(255,255,255,.5);
+ box-shadow: 0 4px 12px rgba(0,0,0,.4), inset 0 1px 0 rgba(255,255,255,.5);
+ -webkit-transition: width .2s linear;
+ -moz-transition: width .2s linear;
+ -o-transition: width .2s linear;
+ transition: width .2s linear;
+}
+
+ .help-modal-content {
+ padding: 0 20px;
+ }
+
+ .help-close {
+ position: absolute;
+ top: .4em;
+ right: .5em;
+ font-size: 1.8em;
+ cursor: pointer;
+ -webkit-transition: color .2s linear;
+ -moz-transition: color .2s linear;
+ -o-transition: color .2s linear;
+ transition: color .2s linear;
+ }
+
+ .help-close:hover {
+ color: #000;
+ }
+
+.help-modal h1 {
+ text-align: center;
+ margin: .5em;
+ font-size: 1.5em;
+ padding-bottom: .4em;
+ border-bottom: solid 2px #ccc;
+ font-weight: normal;
+}
+
+ .help-modal h1 .help-key {
+ float: none;
+ line-height: 1.5;
+ position: relative;
+ bottom: 4px;
+ }
+
+.help-isVisible {
+ opacity: 1;
+ visibility: visible;
+ height: auto;
+ z-index: 8888;
+}
+
+.ie8 .help-underlay.help-isVisible {
+ visibility: visible;
+}
+
+.help-list-wrap {
+ overflow: hidden;
+ margin: 0 auto;
+ -webkit-transition: width .2s linear;
+ -moz-transition: width .2s linear;
+ -o-transition: width .2s linear;
+ transition: width .2s linear;
+}
+
+.help-list {
+ list-style: none;
+ margin: 0;
+ padding: 0 0 10px 0;
+ overflow: hidden;
+ float: left;
+ width: 280px;
+}
+
+ .help-list li {
+ margin-right: 40px;
+ }
+
+.help-key-unit {
+ line-height: 1.8;
+ margin-right: 2em;
+ padding: 5px 0;
+}
+
+.help-key {
+ min-width: 60px;
+ float: left;
+ clear: left;
+ position: relative;
+ bottom: 2px;
+}
+
+.help-key span {
+ font-size: 15px;
+ color: #555;
+ display: inline-block;
+ padding: 0 8px;
+ text-align: center;
+ background-color: #eee;
+ background-repeat: repeat-x;
+ background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#f5f5f5), to(#eee));
+ background-image: -webkit-linear-gradient(#f5f5f5 0%, #eee 100%);
+ background-image: -moz-linear-gradient(#f5f5f5 0%, #eee 100%);
+ background-image: -o-linear-gradient(#f5f5f5 0%, #eee 100%);
+ background-image: linear-gradient(#f5f5f5 0%, #eee 100%);
+ border: 1px solid #ccc;
+ -webkit-border-radius: 3px;
+ -moz-border-radius: 3px;
+ border-radius: 3px;
+ -webkit-box-shadow: inset 0 1px 0 #fff, 0 1px 0 #ccc;
+ box-shadow: inset 0 1px 0 #fff, 0 1px 0 #ccc;
+}
+
+.help-key-def {
+ display: inline-block;
+ margin-left: 1em;
+}
\ No newline at end of file
--- /dev/null
+<div id="helpUnderlay" class="help-underlay">
+ <div id="helpModal" class="help-modal">
+ <h1>Keyboard Shortcuts <kbd class="help-key"><span>?</span></kbd></h1>
+ <div id="helpClose" class="help-close">×</div><!-- .help-close -->
+
+ <div id="helpModalContent" class="help-modal-content">
+
+ <div id="helpListWrap" class="help-list-wrap">
+
+ <!--
+ Each <ul> below creates a column, as long as each has a class of
+ "help-list". Each <li> is a single key/definition pair.
+ The extra nested <span> is to help keep the definitions lined up
+ vertically, for aesthetic reasons.
+ If you hate the extra <span>, just remove it from each key/def pair.
+ -->
+
+ <ul class="help-list">
+
+ <li class="help-key-unit">
+ <kbd class="help-key"><span>space</span></kbd>
+ <span class="help-key-def">Pause/Play</span>
+ </li>
+ <li class="help-key-unit">
+ <kbd class="help-key"><span>r</span></kbd>
+ <span class="help-key-def">Return to start</span>
+ </li>
+ <li class="help-key-unit">
+ <kbd class="help-key"><span>b</span></kbd>
+ <span class="help-key-def">Rewind 10 secs</span>
+ </li>
+ <li class="help-key-unit">
+ <kbd class="help-key"><span>f</span></kbd>
+ <span class="help-key-def">Fast forward 10 secs</span>
+ </li>
+ <li class="help-key-unit">
+ <kbd class="help-key"><span>a</span></kbd>
+ <span class="help-key-def">Mark start</span>
+ </li>
+ <li class="help-key-unit">
+ <kbd class="help-key"><span>c</span></kbd>
+ <span class="help-key-def">Clear start</span>
+ </li>
+
+ </ul><!-- .help-list -->
+
+ </div><!-- .help-list-wrap -->
+
+ </div><!-- .help-modal-content -->
+ </div><!-- .help-modal -->
+</div><!-- .help-underlay -->
\ No newline at end of file
--- /dev/null
+(function(){"use strict";function e(e){e.className=e.className.replace(/help-isVisible*/g,"");e.className=e.className.trim()}function t(){var e=window,t=document,n=t.documentElement,r=t.getElementsByTagName("body")[0],i=e.innerWidth||n.clientWidth||r.clientWidth;return i}function n(){var e=document;return Math.max(e.body.scrollHeight,e.documentElement.scrollHeight,e.body.offsetHeight,e.documentElement.offsetHeight,e.body.clientHeight,e.documentElement.clientHeight)}function r(e){e.helpColsTotal=0;for(e.i=0;e.i<e.helpLists.length;e.i+=1){if(e.helpLists[e.i].className.indexOf("help-list")!==-1){e.helpColsTotal+=1}e.helpListsHeights[e.i]=e.helpLists[e.i].offsetHeight}e.maxHeight=Math.max.apply(Math,e.helpListsHeights);if(t()<=1180&&t()>630&&e.helpColsTotal>2){e.helpColsTotal=2;e.maxHeight=e.maxHeight*e.helpColsTotal}if(t()<=630){e.maxHeight=e.maxHeight*e.helpColsTotal;e.helpColsTotal=1}e.helpListWrap.style.offsetWidth=e.helpList.offsetWidth*e.helpColsTotal+"px";e.helpListWrap.style.height=e.maxHeight+"px";e.helpModal.style.width=e.helpList.offsetWidth*e.helpColsTotal+60+"px";e.helpModal.style.height=e.maxHeight+100+"px"}function i(e){e=e||window.event;var t=e.keyCode||e.which;return t}function s(){var t=document.getElementById("helpUnderlay"),s=document.getElementById("helpModal"),o=document.getElementById("helpClose"),u=null,a={i:null,maxHeight:null,helpListWrap:document.getElementById("helpListWrap"),helpList:document.querySelector(".help-list"),helpLists:document.querySelectorAll(".help-list"),helpModal:s,helpColsTotal:null,helpListsHeights:[]},f;r(a);document.addEventListener("keypress",function(e){if(i(e)===63){f=document.getElementById("helpUnderlay").className;if(f.indexOf("help-isVisible")===-1){document.getElementById("helpUnderlay").className+=" help-isVisible"}}t.style.height=n()+"px"},false);document.addEventListener("keyup",function(n){if(i(n)===27){e(t)}},false);t.addEventListener("click",function(){e(t)},false);s.addEventListener("click",function(e){e.stopPropagation()},false);o.addEventListener("click",function(){e(t)},false);window.onresize=function(){if(u!==null){clearTimeout(u)}u=setTimeout(function(){r(a)},100)}}function o(){var e=false;if(window.XMLHttpRequest){e=new XMLHttpRequest}return e}function u(e,t){if(!document.getElementById("helpUnderlay")){document.getElementsByTagName("body")[0].innerHTML+=e;t()}}function a(e){if(e.readyState===4){if(e.status===200||e.status===304){var t=e.responseText;u(t,function(){s()})}}}function f(){var e=o();if(e){e.onreadystatechange=function(){a(e)};e.open("POST","question.mark.html",true);e.setRequestHeader("Content-Type","application/x-www-form-urlencoded");e.send(null)}else{document.getElementsByTagName("body")[0].innerHTML+="Error: Your browser does not support Ajax"}}f()})()
\ No newline at end of file
--- /dev/null
+body {
+ background-image: url('/songbook/background.jpg');
+ padding: 0px;
+ border: 0px;
+ margin: 0px;
+}
+#title {
+ text-align: center;
+ font-family: Arial, Helvetica;
+ font-size: 28pt;
+}
+#artist {
+ text-align: center;
+ font-family: Arial, Helvetica;
+ font-size: 22pt;
+}
+#song {
+ padding-top: 100px;
+ padding-left: 20px;
+}
+#heading {
+ border: 0;
+ border-bottom: 1px solid black;
+ position: fixed;
+ margin: 0;
+ padding: 0;
+ background-color: #ddd;
+ width: 100%;
+}
+.lyrics, .lyrics_chorus {
+ font-size: 22pt;
+}
+.lyrics_tab, .lyrics_chorus_tab {
+ font-family: "Courier New", Courier;
+ font-size: 18pt;
+}
+.lyrics_chorus, .lyrics_chorus_tab, .chords_chorus, .chords_chorus_tab {
+ font-weight: bold;
+}
+.chords, .chords_chorus, .chords_tab, .chords_chorus_tab {
+ font-size: 18pt;
+ color: blue;
+ padding-right: 4pt;
+}
+.comment, .comment_italic {
+ color: #999;
+ font-size: 18pt;
+}
+.comment_box {
+ background-color: #ffbbaa;
+ text-align: center;
+}
+.comment_italic {
+ font-style: italic;
+}
+.comment_box {
+ border: solid;
+}
--- /dev/null
+// Javascript functions for controling audio
+starttime = null;
+endtime = null;
+
+spacebar = 32;
+return2start = 82;
+backfewsecs = 66;
+forwardfewsecs = 70;
+seta = 65;
+cleara = 67;
+howmanysecs = 10;
+
+window.onload = function() {
+ song = document.getElementById('song');
+
+ starttime = song.currentTime;
+ endtime = song.duration;
+ body = document.getElementsByTagName('body')[0]
+
+ body.onkeydown =
+ function(e) {
+ var ev = e || event;
+ if (ev.keyCode == spacebar) {
+ if (song.paused) {
+ playing = false;
+ } else {
+ playing = true;
+ } // if
+
+ if (playing) {
+ song.pause();
+ playing = false;
+ } else {
+ if (starttime != 0) {
+ song.currentTime = starttime
+ } // if
+
+ song.play();
+ playing = true;
+ } // if
+
+ e.preventDefault();
+ return;
+ } else if (ev.keyCode == return2start) {
+ if (starttime != null) {
+ song.currentTime = starttime;
+ } else {
+ song.currentTime = 0;
+ } // if
+
+ return;
+ } else if (ev.keyCode == backfewsecs) {
+ song.currentTime -= howmanysecs;
+ song.play()
+
+ return;
+ } else if (ev.keyCode == forwardfewsecs) {
+ song.currentTime += howmanysecs;
+ song.play();
+
+ return;
+ } else if (ev.keyCode == seta) {
+ starttime = song.currentTime;
+
+ return;
+ } else if (ev.keyCode == cleara) {
+ starttime = 0;
+
+ return;
+ } // if
+ } // function
+ } // getElementByTagName
\ No newline at end of file
# 2003-08-03 Version 1.1 Uses stylesheets
# 2014-02-05 Added things particular to my implementation of Songbook at
# http://defaria.com/songbook
-
use strict;
use warnings;
sub debug ($) {
my ($msg) = @_;
-
+
return unless $debug;
-
+
print "<font color=red><b>Debug:</b></font> $msg<br>";
-
+
return;
} # debug
-sub warning ($) {\r
+sub warning ($) {
my ($msg) = @_;
debug "warning";
print "<font color=orange><b>Warning</b></font> $msg<br>";
- return;
+ return;
} # warning
sub error {
my ($msg) = @_;
-
+
print "<html><head><title>Web Chord: Error</title></head>" .
"<body><h1>Error</h1><p>\n$msg\n</p>" .
"</body></html>";
-
+
exit;
} # error
my ($song) = @_;
debug "ENTER musicFileExists ($song)";
-
+
my $title = fileparse ($song, qr/\.pro/);
my $musicfile = "/opt/media/$title.mp3";
if (-r $musicfile) {
debug "Exists!";
-
+
return $title;
} else {
debug "Could not find $musicfile";
-
+
return undef;
} # if
} # musicFileExists
my ($chopro, $song) = @_;
my $title = musicFileExists $song;
-
+
# If there's no corresponding music file then do nothing
return unless $title;
-
+
# If the .pro file already has musicpath then do nothing
if ($chopro =~ /\{musicpath:.*\}/) {
debug "$song already has musicpath";
# Otherwise append the musicpath
my $songfile;
-
+
open $songfile, '>>', $song
or undef $songfile;
-
+
unless (defined $songfile) {
my $msg = "Unable to open $song for append - $!<br>";
$msg .= "<br>Please notify <a href=\"mailto:adefaria\@gmail.com?subject=Please chmod 666 $song\">Andrew DeFaria</a> so this can be corrected.<br>";
$msg .= "<br>Thanks";
warning $msg;
-
+
return;
} # unless
my $songbase = '/sdcard';
-
+
print $songfile "{musicpath:$songbase/SongBook/Media/$title.mp3}\n";
-
+
close $songfile;
- return;
+ return;
} # updateMusicPath
# Outputs the HTML code of the chordpro file in the first parameter
$chopro =~ s/\>/\>/g; # replace > with >
$chopro =~ s/\&/\&/g; # replace & with &
- my $title;
-
- if(($chopro =~ /^{title:(.*)}/mi) || ($chopro =~ /^{t:(.*)}/mi)) {
+ my $title = "ChordPro Song";
+ my $artist = "Unknown";
+
+ if (($chopro =~ /^{title:(.*)}/mi) || ($chopro =~ /^{t:(.*)}/mi)) {
$title = $1;
- } else {
- $title = "ChordPro song";
- }
+ } # if
+
+ if (($chopro =~ /^{subtitle:(.*)}/mi) || ($chopro =~ /^{st:(.*)}/mi)) {
+ $artist = $1;
+ } # if
+
print <<END;
<html>
<head>
<title>$title</title>
-<style type="text/css">
-body {
- background-image: url('/songbook/background.jpg');
- padding-left: 100px;
-}
-h1 {
- text-align: center;
- font-family: Arial, Helvetica;
- font-size: 28pt;
- line-height: 10%;
-}
-h2 {
- text-align: center;
- font-family: Arial, Helvetica;
- font-size: 22pt;
- line-height: 50%;
-}
-.lyrics, .lyrics_chorus {
- font-size: 22pt;
-}
-.lyrics_tab, .lyrics_chorus_tab {
- font-family: "Courier New", Courier;
- font-size: 18pt;
-}
-.lyrics_chorus, .lyrics_chorus_tab, .chords_chorus, .chords_chorus_tab {
- font-weight: bold;
-}
-.chords, .chords_chorus, .chords_tab, .chords_chorus_tab {
- font-size: 18pt;
- color: blue;
- padding-right: 4pt;
-}
-.comment, .comment_italic {
- color: #999;
- font-size: 18pt;
-}
-.comment_box {
- background-color: #ffbbaa;
- text-align: center;
-}
-.comment_italic {
- font-style: italic;
-}
-.comment_box {
- border: solid;
-}
-</style>
+<link rel="stylesheet" type="text/css" href="songbook.css">
+<link rel="stylesheet" type="text/css" href="question.mark.css">
+<script src="songbook.js"></script>
+<script src="question.mark.js"></script>
</head>
+
<body>
END
$title = musicFileExists $song;
-
+
if ($title) {
updateMusicpath $chopro, $song;
} # if
-
+
print << "END";
-<table border="0" width="100%">
+<table id="heading">
<tbody>
<tr>
<td align="left"><a href="/songbook"><img src="/Icons/Home.png" alt="Home"></a></td>
-END
-
- if ($title) {
- print <<"END";
-<td align="right">
-<audio controls autoplay>
- <source src="http://defaria.com/Media/$title.mp3" type='audio/mp3'>
- <p>Your user agent does not support the HTML5 Audio element.</p>
-</audio>
-</td>
-END
- } # if
-print <<"END";
+ <td><div id="title">$title</div>
+ <div id="artist"><a href="/songbook/displayartist.php?artist=$artist">$artist</a></div></td>
+ <td align="right" width="300px">
+ <audio id="song" controls autoplay style="padding:0; margin:0">
+ <source src="http://defaria.com/Media/$title.mp3" style="padding:0; margin:0" type='audio/mp3'>
+ Your user agent does not support the HTML5 Audio element.
+ </audio>
+ </td>
</tr>
</tbody>
</table>
+<div id="song">
END
my $mode = 0; # mode defines which class to use
$_ = $1;
chomp;
- if(/^#(.*)/) { # a line starting with # is a comment
- print "<!--$1-->\n"; # insert as HTML comment
- } elsif(/{(.*)}/) { # this is a command
+ if (/^#(.*)/) { # a line starting with # is a comment
+ print "<!--$1-->\n"; # insert as HTML comment
+ } elsif (/{(.*)}/) { # this is a command
$_ = $1;
- if(/^title:/i || /^t:/i) { # title
- print "<H1>$'</H1>\n";
- } elsif(/^subtitle:/i || /^st:/i) { # subtitle
- print "<H2>$'</H2>\n";
- } elsif(/^start_of_chorus/i || /^soc/i) { # start_of_chorus
+ if (/^start_of_chorus/i || /^soc/i) { # start_of_chorus
$mode |= 1;
- } elsif(/^end_of_chorus/i || /^eoc/i) { # end_of_chorus
+ } elsif (/^end_of_chorus/i || /^eoc/i) { # end_of_chorus
$mode &= ~1;
- } elsif(/^comment:/i || /^c:/i) { # comment
+ } elsif (/^comment:/i || /^c:/i) { # comment
print "<span class=\"comment\">($')</span>\n";
- } elsif(/^comment_italic:/i || /^ci:/i) { # comment_italic
+ } elsif (/^comment_italic:/i || /^ci:/i) { # comment_italic
print "<span class=\"comment_italic\">($')</span>\n";
- } elsif(/^comment_box:/i || /^cb:/i) { # comment_box
+ } elsif (/^comment_box:/i || /^cb:/i) { # comment_box
print "<P class=\"comment_box\">$'</P>\n";
- } elsif(/^start_of_tab/i || /^sot/i) { # start_of_tab
+ } elsif (/^start_of_tab/i || /^sot/i) { # start_of_tab
$mode |= 2;
- } elsif(/^end_of_tab/i || /^eot/i) { # end_of_tab
+ } elsif (/^end_of_tab/i || /^eot/i) { # end_of_tab
$mode &= ~2;
} else {
print "<!--Unsupported command: $_-->\n";
}
push(@lyrics,$_); # rest of line (after last chord) into @lyrics
- if($lyrics[0] eq "") { # line began with a chord
+ if ($lyrics[0] eq "") { # line began with a chord
shift(@chords); # remove first item
shift(@lyrics); # (they are both empty)
}
- if(@lyrics==0) { # empty line?
+ if (@lyrics==0) { # empty line?
print "<BR>\n";
- } elsif(@lyrics==1 && $chords[0] eq "") { # line without chords
- print "<DIV class=\"$lClasses[$mode]\">$lyrics[0]</DIV>\n";
+ } elsif (@lyrics==1 && $chords[0] eq "") { # line without chords
+ print "<div class=\"$lClasses[$mode]\">$lyrics[0]</div>\n";
} else {
- print "<TABLE cellpadding=0 cellspacing=0>";
- print "<TR>\n";
+ print "<table cellpadding=0 cellspacing=0>";
+ print "<tr>\n";
my($i);
for($i = 0; $i < @chords; $i++) {
- print "<TD class=\"$cClasses[$mode]\">$chords[$i]</TD>";
+ print "<td class=\"$cClasses[$mode]\">$chords[$i]</td>";
}
- print "</TR>\n<TR>\n";
+ print "</tr>\n<tr>\n";
for($i = 0; $i < @lyrics; $i++) {
- print "<TD class=\"$lClasses[$mode]\">$lyrics[$i]</TD>";
+ print "<td class=\"$lClasses[$mode]\">$lyrics[$i]</td>";
}
- print "</TR></TABLE>\n";
+ print "</tr></table>\n";
} # if
} # if
} # while
+
+ print "</div>";
} # chordpro2html
## Main
print header;
unless ($infile) {
- error "No chordpro parameter";
+ error "No chordpro parameter";
} # unless
open my $file, '<', $infile
print end_html();
-exit;
-
-
+exit;
\ No newline at end of file