Backend for songs.zachdecook.com
* Tabs Project: Initial Commit
Zach DeCook 2018-02-13
commit df6ffc3
-rw-r--r--.gitmodules3
m---------ccharter0
-rw-r--r--functions.js17
-rw-r--r--index.css219
-rw-r--r--index.less131
-rwxr-xr-xindex.php111
-rw-r--r--jsonly.js110
-rw-r--r--page.js111
-rw-r--r--page.php400
-rw-r--r--toc-filter.js18
10 files changed, 1120 insertions, 0 deletions
diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 0000000..1d7efec
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,3 @@
+[submodule "ccharter"]
+ path = ccharter
+ url = https://github.com/earboxer/ccharter.git
diff --git a/ccharter b/ccharter
new file mode 160000
+Subproject dffd1beac4f5fa588efd324924c9e55a22e7d57
diff --git a/functions.js b/functions.js
new file mode 100644
index 0000000..63480a3
--- /dev/null
+++ b/functions.js
@@ -0,0 +1,17 @@
+function array_flip (trans) { // eslint-disable-line camelcase
+ // discuss at: http://locutus.io/php/array_flip/
+ // original by: Kevin van Zonneveld (http://kvz.io)
+ // improved by: Pier Paolo Ramon (http://www.mastersoup.com/)
+ // improved by: Brett Zamir (http://brett-zamir.me)
+ // example 1: array_flip( {a: 1, b: 1, c: 2} )
+ // returns 1: {1: 'b', 2: 'c'}
+ var key
+ var tmpArr = {}
+ for (key in trans) {
+ if (!trans.hasOwnProperty(key)) {
+ continue
+ }
+ tmpArr[trans[key]] = key
+ }
+ return tmpArr
+} \ No newline at end of file
diff --git a/index.css b/index.css
new file mode 100644
index 0000000..61f21d0
--- /dev/null
+++ b/index.css
@@ -0,0 +1,219 @@
+pre {
+ font-weight: bold;
+ font-size: 1em;
+}
+.tabs,
+.chord0,
+.chordC {
+ /*Red Apple*/
+ color: #c00000;
+ font-weight: bold;
+}
+.chord1,
+.chord-11,
+.chordDb {
+ color: #f7102c;
+ text-shadow: 1px 1px 1px black;
+}
+.chord2,
+.chord-10,
+.chordD {
+ color: #ff6400;
+}
+.chord3,
+.chord-9,
+.chordEb {
+ color: #fcb30f;
+ text-shadow: 1px 1px 1px black;
+}
+.chord4,
+.chord-8,
+.chordE {
+ color: #c8c800;
+}
+.chord5,
+.chord-7,
+.chordF {
+ color: #d0ff14;
+ text-shadow: 1px 1px 1px black;
+}
+.chord6,
+.chord-6,
+.chordGb {
+ /*Green Apple*/
+ color: #64c000;
+}
+.chord7,
+.chord-5,
+.chordG {
+ /*Lime*/
+ color: #00dc00;
+}
+.chord8,
+.chord-4,
+.chordAb {
+ /*Blue Razzberry*/
+ color: #0096c8;
+}
+.chord9,
+.chord-3,
+.chordA {
+ /*Blueberry*/
+ color: #0000c8;
+}
+.chord10,
+.chord-2,
+.chordBb {
+ color: #9600be;
+}
+.chord11,
+.chord-1,
+.chordB {
+ color: #e7417c;
+}
+.btn-Z {
+ margin-top: 1em;
+ color: white;
+ background-color: #999;
+ border-color: #777777;
+}
+.btn-Z:hover {
+ color: white;
+ background-color: #888888;
+ border-color: #666666;
+}
+.btn-C {
+ margin-top: 1em;
+ color: white;
+ background-color: #c00000;
+ border-color: #9e0000;
+}
+.btn-C:hover {
+ color: white;
+ background-color: #af0000;
+ border-color: #8d0000;
+}
+.btn-Db {
+ margin-top: 1em;
+ color: white;
+ background-color: #f7102c;
+ border-color: #d5000a;
+ text-shadow: 1px 1px 1px black;
+}
+.btn-Db:hover {
+ color: white;
+ background-color: #e6001b;
+ border-color: #c40000;
+}
+.btn-D {
+ margin-top: 1em;
+ color: white;
+ background-color: #ff6400;
+ border-color: #dd4200;
+}
+.btn-D:hover {
+ color: white;
+ background-color: #ee5300;
+ border-color: #cc3100;
+}
+.btn-Eb {
+ margin-top: 1em;
+ color: white;
+ background-color: #fcb30f;
+ border-color: #da9100;
+ text-shadow: 1px 1px 1px black;
+}
+.btn-Eb:hover {
+ color: white;
+ background-color: #eba200;
+ border-color: #c98000;
+}
+.btn-E {
+ margin-top: 1em;
+ color: white;
+ background-color: #c8c800;
+ border-color: #a6a600;
+}
+.btn-E:hover {
+ color: white;
+ background-color: #b7b700;
+ border-color: #959500;
+}
+.btn-F {
+ margin-top: 1em;
+ color: white;
+ background-color: #d0ff14;
+ border-color: #aedd00;
+ text-shadow: 1px 1px 1px black;
+}
+.btn-F:hover {
+ color: white;
+ background-color: #bfee03;
+ border-color: #9dcc00;
+}
+.btn-Gb {
+ margin-top: 1em;
+ color: white;
+ background-color: #64c000;
+ border-color: #429e00;
+}
+.btn-Gb:hover {
+ color: white;
+ background-color: #53af00;
+ border-color: #318d00;
+}
+.btn-G {
+ margin-top: 1em;
+ color: white;
+ background-color: #00dc00;
+ border-color: #00ba00;
+}
+.btn-G:hover {
+ color: white;
+ background-color: #00cb00;
+ border-color: #00a900;
+}
+.btn-Ab {
+ margin-top: 1em;
+ color: white;
+ background-color: #0096c8;
+ border-color: #0074a6;
+}
+.btn-Ab:hover {
+ color: white;
+ background-color: #0085b7;
+ border-color: #006395;
+}
+.btn-A {
+ margin-top: 1em;
+ color: white;
+ background-color: #0000c8;
+ border-color: #0000a6;
+}
+.btn-A:hover {
+ color: white;
+ background-color: #0000b7;
+ border-color: #000095;
+}
+.btn-Bb {
+ margin-top: 1em;
+ color: white;
+ background-color: #9600be;
+ border-color: #74009c;
+}
+.btn-Bb:hover {
+ color: white;
+ background-color: #8500ad;
+ border-color: #63008b;
+}
+.btn-B {
+ margin-top: 1em;
+ color: white;
+ background-color: #e7417c;
+ border-color: #c51f5a;
+}
+.btn-B:hover {
+ color: white;
+ background-color: #d6306b;
+ border-color: #b40e49;
+}
diff --git a/index.less b/index.less
new file mode 100644
index 0000000..2ba475a
--- /dev/null
+++ b/index.less
@@ -0,0 +1,131 @@
+@Red-Apple: rgb(192,0,0);
+@Cherry: rgb(255,0,0);
+@Strawberry: rgb(247, 16, 44);
+@Grapefruit: rgb(231,91,40);//Going into retirement.
+@Orange: rgb(255,100,0);
+@Banana: rgb(150,150,0);//Going into retirement.
+@Mango: rgb(252, 179, 15);
+@Lemon: rgb(200,200,0);//This color is not accurately named.
+@Pear: rgb(208, 255, 20);//Too bright to see!
+@Green-Apple: rgb(100,192,0);//Going into retirement.
+@Lime: rgb(0,220,0);
+@Blue-Razzberry: rgb(0,150,200);
+@Blueberry: rgb(0,0,200);
+@Blackberry: rgb(50,0,100);//Going into retirement.
+@Grape: rgb(150,0,190);
+@Raspberry: rgb(231, 65, 124);
+
+@ColorC: @Red-Apple;
+@ColorDb: @Strawberry;
+@ColorD: @Orange;
+@ColorEb: @Mango;
+@ColorE: @Lemon;
+@ColorF: @Pear;
+@ColorGb: @Green-Apple;
+@ColorG: @Lime;
+@ColorAb: @Blue-Razzberry;
+@ColorA: @Blueberry;
+@ColorBb: @Grape;
+@ColorB: @Raspberry;
+pre {
+ font-weight: bold;
+ font-size: 1em;
+}
+.chordZ(@color: #BBB){
+ color: @color;
+ & when(hsvvalue(@color) > 95%) and (lightness(@color) > 51%){
+ text-shadow: 1px 1px 1px black;
+ }
+}
+.tabs, .chord0, .chordC{/*Red Apple*/
+ .chordZ(@ColorC);
+ font-weight: bold;
+}
+.chord1, .chord-11, .chordDb{
+ .chordZ(@ColorDb)
+}
+.chord2, .chord-10, .chordD{
+ .chordZ(@ColorD);
+}
+.chord3, .chord-9, .chordEb{
+ .chordZ(@ColorEb);
+}
+.chord4, .chord-8, .chordE{
+ .chordZ(@ColorE);
+}
+.chord5, .chord-7, .chordF{
+ .chordZ(@ColorF);
+}
+.chord6, .chord-6, .chordGb{/*Green Apple*/
+ .chordZ(@ColorGb);
+}
+.chord7, .chord-5, .chordG{/*Lime*/
+ .chordZ(@ColorG);
+}
+.chord8, .chord-4, .chordAb{/*Blue Razzberry*/
+ .chordZ(@ColorAb);
+}
+.chord9, .chord-3, .chordA{/*Blueberry*/
+ .chordZ(@ColorA);
+}
+.chord10, .chord-2, .chordBb{
+ .chordZ(@ColorBb);
+}
+.chord11, .chord-1, .chordB{
+ .chordZ(@ColorB);
+}
+
+.btn-Z(@color: #999){
+ margin-top: 1em;
+ color: white;
+ background-color: @color;
+ border-color: @color - #222;
+ & when(hsvvalue(@color) > 95%) and (lightness(@color) > 51%){
+ text-shadow: 1px 1px 1px black;
+ }
+ &:hover{
+ color: white;
+ background-color: @color - #111;
+ border-color: @color - #333;
+ }
+}
+.btn-Z{
+ .btn-Z;
+};
+
+.btn-C{
+ .btn-Z(@ColorC);
+}
+.btn-Db{
+ .btn-Z(@ColorDb);
+}
+.btn-D{
+ .btn-Z(@ColorD);
+}
+.btn-Eb{
+ .btn-Z(@ColorEb);
+}
+.btn-E{
+ .btn-Z(@ColorE);
+}
+.btn-F{
+ .btn-Z(@ColorF);
+}
+.btn-Gb{
+ .btn-Z(@ColorGb);
+}
+.btn-G{
+ .btn-Z(@ColorG);
+}
+.btn-Ab{
+ .btn-Z(@ColorAb);
+}
+.btn-A{
+ .btn-Z(@ColorA);
+}
+.btn-Bb{
+ .btn-Z(@ColorBb);
+}
+.btn-B{
+ .btn-Z(@ColorB);
+} \ No newline at end of file
diff --git a/index.php b/index.php
new file mode 100755
index 0000000..bc25f51
--- /dev/null
+++ b/index.php
@@ -0,0 +1,111 @@
+<!--
+index.php contains the main html used for creating the page.
+-->
+<!DOCTYPE html>
+<html>
+ <head>
+ <!-- Latest compiled and minified CSS -->
+ <!--<link rel="stylesheet" href="../shared/bootstrap-3.3.6/css/bootstrap.min.css"
+ integrity="sha384-1q8mTJOASx8j1Au+a5WDVnPi2lkFfwwEAa8hDDdjZlpLegxhjVME1fgjWPGmkzs7"
+ crossorigin="anonymous"> -->
+ <link rel="stylesheet"
+ href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css">
+
+ <link rel="stylesheet" href="index.css" >
+ <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
+ <meta name="viewport" content="width=device-width, initial-scale=.65">
+ <title>Choruses and Hymns</title>
+ </head>
+
+ <p><body>
+
+<!-- Light yellow box around page contents -->
+<div class="col-lg-offset-2 col-lg-8 col-md-offset-1 col-md-10 col-sm-12 col-xs-12 bg-success">
+
+ <div class = "col-xs-6 col-xs-offset-0 ">
+ <h3>Browse Songs</h3>
+ <a href="?song=0">Table of contents</a>
+ <form>
+ <input name='song' type='number' value='<?php echo isset($_GET['song']) ? $_GET['song'] : '' ?>'
+ min='0' max='169'
+ />
+ <input type="submit" value="Jump to song" />
+ </form>
+ </div>
+
+ <div class = "col-xs-6 col-xs-offset-0 ">
+ <div id="chordarea">
+ <canvas id='chordy' width="100" height="100"/>
+ </div>
+ <i>Experimental: Click on a chord to view guitar tablature</i>
+ <div id="messages"></div>
+ </div>
+
+ <div class = "col-sm-0 col-md-2 col-lg-2"><p></p></div>
+ <div class = "col-lg-12 col-md-12 col-sm-12 col-xs-12"><p></p></div>
+
+ <br></br>
+</div>
+<div class = "text-center col-lg-offset-2 col-lg-8 col-md-offset-1 col-md-10 col-sm-12 col-xs-12 bg-info ">
+ <form>
+ <?php $transp = isset( $_GET['transp']) ? (int)$_GET['transp'] : 0 ?>
+ <select name="transp" id="transp"
+ value = "<?php echo $transp;?>"
+ >
+ <?php
+ for ($i=-6; $i < 12; $i++) {
+ if (($transp + 24)%12 == $i) $selected = 'selected';
+ else $selected = '';
+ //$dir = ($i >= 0 ? "up" : "down" );
+ $dir = "transpose";
+ echo "<option value='$i' $selected>$dir $i semitones</option>";
+ }
+ ?>
+ </select>
+ <noscript>
+ <button>Transpose</button>
+ <?php
+ //$val1 = ($_GET['transp'] - 1 + 12)%12;
+ $val2 = ($transp + 2)%12;
+ //$val3 = ($_GET['transp'] + 3)%12;
+ //echo "<input type='submit' value='$val1' name='transp'>";
+ echo "<input type='submit' value='$val2' name='transp'>";
+ //echo "<input type='submit' value='$val3' name='transp'>";
+ ?>
+ </noscript>
+ </form>
+</div>
+
+
+<div class="col-lg-offset-2 col-lg-8 col-md-offset-1 col-md-10 col-sm-12 col-xs-12 bg-info">
+
+<?php
+include 'page.php';
+
+ $song_number = isset( $_GET['song'] ) ? $_GET['song'] : '';
+ if( ! $song_number )
+ {
+ echo toc();
+ }
+ else
+ {
+ echo load_song( $song_number, ($transp + 24)%12 );
+ }
+
+?>
+<br/><br><br>
+All songs are owned by their respective copyright holders. No infringement intended.
+</div>
+
+<!-- <script type="text/javascript" src="../scripts/jqm.js"></script> -->
+<script src="https://code.jquery.com/jquery-1.12.4.min.js"
+integrity="sha256-ZosEbRLbNQzLpnKIkEdrPv7lOy9C27hHQ+Xp8a4MxAQ="
+crossorigin="anonymous"></script>
+<script src="toc-filter.js"></script>
+<script type="text/javascript" src="functions.js"></script>
+<script type="text/javascript" src="page.js"></script>
+<script type="text/javascript" src="jsonly.js"></script>
+<script type="text/javascript" src="ccharter/scripts/ccharter.js"></script>
+<script type="text/javascript" src="data/chords.js"></script>
+ </body>
+</html>
diff --git a/jsonly.js b/jsonly.js
new file mode 100644
index 0000000..9e1cf2d
--- /dev/null
+++ b/jsonly.js
@@ -0,0 +1,110 @@
+/**
+ * @brief jsonly.js contains the functions that are only called in js,
+ * not translated from server-side code.
+ */
+
+$('#transp').change( do_transpose );
+var lastTransp = parseInt($('#transp').val());
+
+function do_transpose()
+{
+ var transp = parseInt($('#transp').val());
+ var neww = transp;
+
+ $(".tabs").each(function(){
+ // Since we only store text that is displayed, transpose relative to
+ // previous transposition.
+ // Requires the class to be "tabs chordXX";
+ var old = parseInt($(this).attr('class').substring(10));
+ if ( isNaN(old) )
+ {
+ old = $(this).attr('class').substring(10);
+ neww = transpadd(old, transp - lastTransp);
+ }
+ $(this).text( zj_transpose2( $(this).text(), transp - lastTransp ) );
+ $(this).removeClass("chord"+old);
+ $(this).addClass("chord"+neww);
+ });
+ $(".btn[data-key]").each(function(){
+ console.log($(this));
+ var oldKey = $(this).attr('data-key');
+ var newKey = transpadd(oldKey, transp - lastTransp);
+ $(this).removeClass('btn-'+oldKey);
+ $(this).addClass('btn-'+newKey);
+ $(this).attr('data-key', newKey);
+ $(this).text( $(this).attr('data-words') + newKey );
+ var tt = parseInt($(this).attr('href').match(/transp=(.+)/)[1]);
+ tt = ( transp - lastTransp + 24 + tt)%12;
+ var newhref = $(this).attr('href').match(/(.*?&transp=).+/)[1] + tt;
+ $(this).attr('href', newhref);
+ })
+ lastTransp = transp;
+}
+
+$(".tabs").click(function(e) {
+ s = window.getSelection();
+ var range = s.getRangeAt(0);
+ var node = s.anchorNode;
+ while (range.toString().indexOf(' ') != 0 ) {
+ if (range.startOffset == 0)
+ break;
+ range.setStart(node, (range.startOffset - 1));
+ }
+ range.setStart(node, Math.max(0,range.startOffset));
+ while (range.toString().lastIndexOf(' ') != range.toString().length - 1
+ || range.toString().length <= 1 )
+ {
+ range.setEnd(node, range.endOffset+1);
+ if ( range.endOffset == node.length)
+ break;
+ }
+ //range.setEnd(Math.min(range.endOffset+1,node.length);
+ var str = range.toString().trim();
+ if( str != "" )
+ show_tab(str);
+});
+
+function show_tab( chord )
+{
+ var canvas = $("#chordy")[0];
+ var context = canvas.getContext('2d');
+ context.clearRect(0, 0, canvas.width, canvas.height);
+
+ getChordFrets(chord);
+
+/*
+ var img = "<img class='myimage' src='chordimages/" + chord + ".png'/>";
+ $("#chordarea").append(img);
+ $(".myimage").error(function(){
+ $(this).hide();
+ });*/
+}
+
+function getChordFrets(chord)
+{
+ $("#messages").html("");
+ chord = chord.replace("(", "");
+ chord = chord.replace(")", "");
+ chord = chord.replace("sus", "s");
+ chord = chord.replace("s4", "s");
+ chord = chord.replace("s", "sus");
+ chord = chord.replace("7sus", "sus7");
+ chord = chord.replace("mj7", "maj7");
+ var chordd = chord;
+ chordd = chordd.replace("Db", "C#");
+ chordd = chordd.replace("Eb", "D#");
+ chordd = chordd.replace("Gb", "F#");
+ chordd = chordd.replace("Ab", "G#");
+ chordd = chordd.replace("Bb", "A#");
+ taco = chorddict[chordd];
+ if ( taco ) {
+ ChordCharter.drawChord("chordy", 30, 25, chord, taco);
+ } else {
+ //Cross-origin stuff
+ /*var url = "http://jguitar.com/chordsearch?chordsearch="+chord+"&labels=none&fretSpan=4";
+ $.get(url, function(data, status){
+ $("#messages").prepend("something something gotten");
+ });*/
+ $("#messages").prepend("Couldn't find a chord for '"+ chord +"'");
+ }
+} \ No newline at end of file
diff --git a/page.js b/page.js
new file mode 100644
index 0000000..8815b63
--- /dev/null
+++ b/page.js
@@ -0,0 +1,111 @@
+/**
+ * @brief page.js tries to improve pageload speeds
+ * by processing client-side.
+ * @author Zach DeCook (zjd7calvin)
+ * @date 12, February 2017
+ * requires jQuery
+ */
+
+/**
+ * @brief zj_transpose2 transposes a line
+ */
+function zj_transpose2( line, transp )
+{
+ var newchords = zj_transparray( transp );
+ var newline = '';
+ var space = 0; ///@< Spaces that need to be added or removed.
+ for(var i = 0; i < line.length; i++)
+ {
+ var chari = line[i];
+ var nchar = line[i+1];
+ var upchar = line[i].toUpperCase();
+ var cval = upchar.charCodeAt(0);
+ // A-G
+ if( cval <= 71 && cval >=65 )
+ {
+ // Exception for Cmaj7
+ if( upchar == 'A' && nchar == 'j' )
+ {
+ newline += chari;
+ }
+ else if( nchar == 'b' || nchar =='#')
+ {
+ i++; //We have read an extra character now.
+ var newchord = newchords[upchar + nchar];
+ if( newchord.length == 1 )
+ {
+ // Need to insert an extra space.
+ space += 1;
+ }
+ newline += newchord;
+ }
+ else
+ {
+ var newchord = newchords[upchar];
+ if( newchord.length == 2 )
+ {
+ // Need to remove an extra space.
+ space -= 1;
+ }
+ newline += newchord;
+ }
+ }
+ else if ( chari == ' ' )
+ {
+ if( space >= 0)
+ {
+ for (var j = 0; j <= space; j++)
+ {
+ newline += ' ';
+ }
+ space = 0;
+ }
+ else
+ {
+ // Only balance negative spaces if one will be left remaining
+ if( nchar == ' ' )
+ {
+ i++;
+ space += 1;
+ }
+ newline += ' ';
+ }
+ }
+ else
+ {
+ newline += chari;
+ }
+ }
+ return newline;
+}
+
+function zj_transparray( transp )
+{
+ var chords =
+ ["C","C#","D","D#","E","F","F#","G","G#","A","Bb","B" ];
+ var newchords = [];
+ // Create array to map old chords to new ones
+ for (var i=0; i < 12; i++)
+ {
+ newchords[chords[i]] = chords[(i+transp+12)%12];
+ }
+ newchords["Db"] = newchords["C#"];
+ newchords["Eb"] = newchords["D#"];
+ newchords["Gb"] = newchords["F#"];
+ newchords["Ab"] = newchords["G#"];
+ newchords["A#"] = newchords["Bb"];
+ return newchords;
+}
+
+function transpadd( fromkey, integer )
+{
+ var chords = array_flip( [ "C","C#","D","D#","E","F","F#","G","G#","A","A#","B" ]);
+ chords["Db"] = chords["C#"];
+ chords["Eb"] = chords["D#"];
+ chords["Gb"] = chords["F#"];
+ chords["Ab"] = chords["G#"];
+ chords["Bb"] = chords["A#"];
+ var ochords = [ "C","Db","D","Eb","E","F","Gb","G","Ab","A","Bb","B" ];
+
+ return ochords[(parseInt(chords[fromkey]) + integer + 24)%12];
+} \ No newline at end of file
diff --git a/page.php b/page.php
new file mode 100644
index 0000000..c77b40e
--- /dev/null
+++ b/page.php
@@ -0,0 +1,400 @@
+<?php
+/**
+ * @author: Zach DeCook
+ * Facilitates loading of tablature and transposition.
+ * Copyright (C) 2017 Zach DeCook
+ */
+
+/**
+ * @brief Go through the file inputfile.txt and return each song with a link to it.
+ */
+function toc(){
+ $toc = '';
+ $toc .= '<form><input type="text" id="toc-filter" placeholder="Filter by song title"/></form>';
+ $toc .= '<ul id="toc">';
+ $handle = fopen("inputfile.txt", "r");
+ if ( $handle ){while (($line = fgets($handle)) !== false)
+ {
+ // Fix weird apostrophe errors
+ $line = stripslashes(
+ mb_convert_encoding( $line, "HTML-ENTITIES", "UTF-8" )
+ );
+ $matches = array();
+ if( preg_match("(^(X?\d+)\. )", $line, $matches))
+ {
+ $toc .= "<li><a href=?song=$matches[1]>" . $line . "</a>";
+ }
+ if( preg_match("/^{Verse: ?(.*)}/i", $line, $matches)) $toc .= " ($matches[1])";
+ if( preg_match("(^{p\d+(\(\S\S?\S?\))?})", $line)) $toc .= " (Reviewed)";
+ if( preg_match( "/\{p?\d*\((.+m?)\)\}/", $line, $matches) ) $toc .= " ($matches[1])";
+
+ }fclose($handle);}
+ else
+ {
+ //Error
+ }
+ $toc .= '</ul>';
+ return $toc;
+}
+
+function refToNum( $ref )
+{
+ $number = 0;
+ $matches = array();
+ if ( preg_match( "/(\w+).?\s*(\d+)?/", $ref, $matches ))
+ {
+ if ( isset($matches[1]) )
+ {
+ switch (strtolower($matches[1])) {
+ case 'gen': $number+=100000;break;
+ case 'ex': $number +=200000;break;
+ case 'rev':
+ $number += 6600000;
+ break;
+
+ default:
+ $number += 0;
+ break;
+ }
+ }
+ }
+ return $number;
+}
+
+/**
+ * @brief load a portion of the text file inputfile.txt starting with
+ * "$number. "
+ * Where $number is an integer, or "X12", etc.
+ */
+function load_song( $number, $transp = 0 )
+{
+ $handle = fopen("inputfile.txt", "r");
+ if( ! $transp ) $transp = 0;
+
+ $current_song = 0;
+ $something = 0;
+ $song = '';
+ $textlines = '';///@< for suggesting songs
+ $allsongs = array();
+ $suggestedSong = array();
+ $songKeys = array();
+
+ if ($handle) {while (($line = fgets($handle)) !== false)
+ {
+ // Fix weird apostrophe errors
+ $line = stripslashes(
+ mb_convert_encoding( $line, "HTML-ENTITIES", "UTF-8" )
+ );
+
+ // If we see a number, then that is what song we are on.
+ $matches = array();
+ if ( preg_match("(^(X?\d+)\. )", $line, $matches) )
+ {
+ $allsongs[$matches[1]] = $line;
+ $current_song = $matches[1];
+ if( rand(0,100) < 5 )
+ {
+ $suggestedSong[$current_song]['title'] = $line;
+ }
+ }
+
+ if( preg_match( "/\{p?\d*\((.+m?)\)\}/", $line, $matches) )
+ {
+ if ( isset( $suggestedSong[$current_song]))
+ {
+ $suggestedSong[$current_song]['key'] = $matches[1];
+ }
+ if ( $current_song === $number )
+ {
+ $songKeys[] = $matches[1];
+ $songKey = $matches[1];
+ }
+ }
+
+ if ( $current_song === $number || $number == "all" )
+ {
+ if( chordline($line) )
+ {
+ if ( $transp != 0)
+ {
+ $line = z_transpose2( $line, $transp );
+ }
+ $class = ! isset( $songKey ) ? "tabs chord$transp" : "tabs chord" . transpadd( $songKey, $transp );
+ $line = str_replace(
+ array('{','}'),
+ array('</b>{', "}<b class='$class'>" ),
+ $line );
+
+ $song .= "<b class='$class'>" . $line . "</b>";
+ }
+ else
+ {
+ $song .= $line;
+ $textlines .= $line;
+ }
+ }
+ }fclose($handle);}
+ else {
+ // error opening the file.
+ }
+
+ return
+ renderEasyTransp( $transp, $number, $songKeys )
+ . "<pre>" . $song . "</pre>\n"
+ . renderNavButtons( $number )
+ . renderSS($suggestedSong, $songKeys, $transp);
+}
+
+function renderEasyTransp( $transp, $num, $songKeys = array() )
+{
+ $s = '';
+ //up two semitones
+ $classT = 'btn col-xs-12';
+ $nsongKey = 'Z';
+ $data = '';
+ $words = "Transposed up 2 semitones";
+ if (isset ( $songKeys[0] ) )
+ {
+ $classT = 'btn col-xs-6';
+ $origKey = $songKeys[0];
+ $presentKey = transpadd( $origKey, $transp );
+ $data = "data-key='$presentKey' data-words='Current Key: '";
+ $s .= "\t<a href='?song=$num&transp=$transp' class='$classT btn-$presentKey' $data>"
+ . "Current Key $presentKey</a>\n";
+ $nsongKey = transpadd( $presentKey, 2) ?: 'Z';
+ $words = "Transposed up to ";
+ $data = "data-key='$nsongKey' data-words='$words'";
+ $words .= $nsongKey;
+ }
+ $tt = $transp + 2;
+ $msg =
+ $s .= "\t<a href='?song=$num&transp=$tt' class='$classT btn-$nsongKey' $data>"
+ . "$words</a>\n";
+ //favorite keys
+ return $s;
+}
+
+function renderNavButtons( $number )
+{
+ $navbuttons = '';
+ if ( is_numeric($number) )
+ {
+ $pnumber = $number - 1;
+ $nnumber = $number + 1;
+ $navbuttons .= "<a href='?song=$pnumber' class='btn btn-Z col-xs-6'>previous</a>\n";
+ $navbuttons .= "<a href='?song=$nnumber' class='btn btn-Z col-xs-6'>next</a>\n";
+ }
+ return $navbuttons;
+}
+
+/**
+ * @brief Show the suggested songs as buttons with transpositions.
+ * @param $suggestedSong array of songs '25' => ['title'=> 'Majesty', 'key' => 'Bb' ]
+ * @param $songKeys Keys that you want to transpose these songs into.
+ */
+function renderSS( $suggestedSong = array(), $songKeys, $transp )
+{
+ $ss = array();
+ $bs = array();// "bad" songs. Songs without transpositions.
+ //shuffle( $suggestedSong );
+ foreach ($suggestedSong as $songNum => $songarray) {
+ if( isset($songarray['key']) )
+ {
+ $ok = trim($songarray['key'], 'm');
+ $n = 2;
+ $bn = 12-($n* count($songKeys));
+ $smn = count($songKeys) < 3 ? 3 : 12/count($songKeys);
+ $smbn = count($songKeys) < 3 ? 12 - count($songKeys) * $smn : 12;
+ $xsn = count($songKeys) == 1 ? 4 : 12/(count($songKeys) ?: 1);
+ $xsbn = count($songKeys) == 1? 8 : 12;
+ //$xtn = 12/count($songKeys);
+ //$xtbn = 12;
+ $s = "<a href='?song=$songNum' class='btn btn-$ok col-md-$bn col-sm-$smbn col-xs-$xsbn'>"
+ . "$songarray[title] ($songarray[key])</a>\n";
+ $classT = "btn col-md-$n col-sm-$smn col-xs-$xsn";
+ foreach( $songKeys as $sK )
+ {
+ $tt = difftransp( $sK, $songarray['key']);
+ if ( is_integer($tt) );
+ {
+ $tt = ($tt + $transp + 12)%12;
+ $nsongKey = transpadd( $sK, $transp );
+
+ $s .= "\t<a href='?song=$songNum&transp=$tt' class='$classT btn-$nsongKey' data-key='$nsongKey' "
+ . "data-words='Transposed to '>"
+ . "Transposed to $nsongKey</a>\n";
+ }
+ }
+ $ss[] = $s;
+ }
+ else
+ {
+ $bs[] = "<a href='?song=$songNum' class='btn btn-Z col-xs-12'>$songarray[title]</a>\n";
+ }
+ }
+ shuffle( $ss ); shuffle( $bs );
+ return implode($ss) . implode($bs);
+}
+
+/**
+ * @brief Determine whether or not this line contains chords.
+ */
+function chordline($line)
+{
+ $chords =
+ array( "C","C#","D","D#","E","F","F#","G","G#","A#","B",//"A",
+ "Db", "Eb", "Gb", "Bb",//"Ab",
+ "Cm", "Dm", "Fm", "Gm", "Bm", //"Em", "Am",
+ );
+ $ambiguous = array( "Ab", "Em", "Am", "A" );
+ $line = str_replace(array('/','7', "\n", '2', '4'), ' ', $line);
+ $tokens = array_filter(explode(' ', $line));
+
+ $badtokens = 0;
+ $ambtokens = 0;
+ foreach ($tokens as $token) {
+ if( in_array( substr($token, 0,2), $chords ) ) return TRUE;
+ else if ( in_array( substr( $token, 0,2), $ambiguous) ) $ambtokens++;
+ else if( $badtokens > 10 ) return FALSE;
+ else $badtokens++;
+ }
+ return $ambtokens >= $badtokens;
+}
+
+function normalizechords($line, $space=TRUE)
+{
+ // Get rid of flats
+ $old = array( "Db", "Eb", "Gb", "Ab", "Bb");
+ $new = array( "C#", "D#", "F#", "G#", "A#");
+ $line = str_replace($old, $new, $line);
+
+ // Uppercase letters A-G
+ $lc = array( "a", "b", "c", "d", "e", "f", "g");
+ $uc = array( "A", "B", "C", "D", "E", "F", "G");
+ $line = str_replace($lc, $uc, $line);
+
+ if ( $space == TRUE )
+ {
+ // Move space for nonsharp chords that didn't end in one
+ $line = preg_replace("( ([A-G])([^# ]))", "$1 $2", $line);
+
+ // Trailing space at the end of the line
+ $line = str_replace("\n", " \n", $line );
+ }
+
+ return $line;
+}
+
+function z_transpose2( $line, $transp )
+{
+ $newchords = z_transparray( $transp );
+ $newline = '';
+ $space = 0; ///@< Spaces that need to be added or removed.
+ for($i = 0; $i < strlen($line); $i++)
+ {
+ $char = $line[$i];
+ $nchar = isset($line[$i+1]) ? $line[$i+1] : '';
+ $upchar = strtoupper($line[$i]);
+ $cval = ord($upchar);
+ // A-G
+ if( $cval <= 71 && $cval >=65 )
+ {
+ // Exception for Cmaj7
+ if( $upchar == 'A' && $nchar == 'j' )
+ {
+ $newline .= $char;
+ }
+ else if( $nchar == 'b' || $nchar =='#')
+ {
+ $i++; //We have read an extra character now.
+ $newchord = $newchords[$upchar . $nchar];
+ if( strlen($newchord) == 1 )
+ {
+ // Need to insert an extra space.
+ $space += 1;
+ }
+ $newline .= $newchord;
+ }
+ else
+ {
+ $newchord = $newchords[$upchar];
+ if( strlen($newchord) == 2 )
+ {
+ // Need to remove an extra space.
+ $space -= 1;
+ }
+ $newline .= $newchord;
+ }
+ }
+ else if ( $char == ' ' )
+ {
+ if( $space >= 0)
+ {
+ for ($j = 0; $j <= $space; $j++)
+ {
+ $newline .= ' ';
+ }
+ $space = 0;
+ }
+ else
+ {
+ // Only balance negative spaces if one will be left remaining
+ if( $nchar == ' ' )
+ {
+ $i++;
+ $space += 1;
+ }
+ $newline .= ' ';
+ }
+ }
+ else
+ {
+ $newline .= $char;
+ }
+ }
+ return $newline;
+}
+
+function z_transparray( $transp )
+{
+ $chords =
+ array( "C","C#","D","D#","E","F","F#","G","G#","A","Bb","B" );
+ $newchords = array();
+ // Create array to map old chords to new ones
+ for ($i=0; $i < 12; $i++)
+ {
+ $newchords[$chords[$i]] = $chords[($i+$transp+12)%12];
+ }
+ $newchords["Db"] = $newchords["C#"];
+ $newchords["Eb"] = $newchords["D#"];
+ $newchords["Gb"] = $newchords["F#"];
+ $newchords["Ab"] = $newchords["G#"];
+ $newchords["A#"] = $newchords["Bb"];
+ return $newchords;
+}
+
+function difftransp( $fromkey, $tokey )
+{
+ $chords = array_flip(array( "C","C#","D","D#","E","F","F#","G","G#","A","A#","B" ));
+ $chords["Db"] = $chords["C#"];
+ $chords["Eb"] = $chords["D#"];
+ $chords["Gb"] = $chords["F#"];
+ $chords["Ab"] = $chords["G#"];
+ $chords["Bb"] = $chords["A#"];
+ $fromkey = trim($fromkey, 'm');
+ $tokey = trim($tokey, 'm');
+ return $chords[$fromkey] - $chords[$tokey];
+}
+
+function transpadd( $fromkey, $integer )
+{
+ $chords = array_flip(array( "C","C#","D","D#","E","F","F#","G","G#","A","A#","B" ));
+ $chords["Db"] = $chords["C#"];
+ $chords["Eb"] = $chords["D#"];
+ $chords["Gb"] = $chords["F#"];
+ $chords["Ab"] = $chords["G#"];
+ $chords["Bb"] = $chords["A#"];
+ $ochords = array( "C","Db","D","Eb","E","F","Gb","G","Ab","A","Bb","B" );
+ $fromkey = trim($fromkey, 'm');
+ return $ochords[($chords[$fromkey] + $integer)%12];
+}
diff --git a/toc-filter.js b/toc-filter.js
new file mode 100644
index 0000000..08afa2d
--- /dev/null
+++ b/toc-filter.js
@@ -0,0 +1,18 @@
+$(function(){
+ $("#toc-filter").bind("change paste keyup", toc_filter);
+});
+
+function toc_filter( event )
+{
+ var val = $(this).val().toLowerCase();
+ $("#toc li").each(function(){
+ if($(this).text().toLowerCase().indexOf(val) == -1)
+ {
+ $(this).hide();
+ }
+ else
+ {
+ $(this).show();
+ }
+ });
+} \ No newline at end of file