<?php

/*
  Ukulele Chord Finder
  by the ru aka Ulf Åström
*/

error_reporting(E_ALL);

@require_once(
"stuff.php");

// First we'll set up some static things.

$note_names = array(0  => "C",  1  => "C#"2  => "D",  3  => "D#",
                    
4  => "E",  5  => "F",  6  => "F#"7  => "G",
                    
8  => "G#"9  => "A",  10 => "A#"11 => "B");

$chord_types =
  array(
    
""       => array(0,  4,  7),
    
"5"      => array(0,  7),
    
"m"      => array(0,  3,  7),
    
"7"      => array(0,  4,  7,  10),
    
"m7"     => array(0,  3,  7,  10),
    
"sus4"   => array(0,  5,  7),
    
"dim"    => array(0,  3,  6),
    
"aug"    => array(0,  4,  8),
    
"maj7"   => array(0,  4,  7,  11),
    
"mmaj7"  => array(0,  3,  7,  11),
    
" major" => array(02457911),
  );

$tunings =
  array(
    
0  => array("name" => "ADF#B",
                
"strings" => array(=> 9,  => 2,  => 6,  => 11)),
    
1  => array("name" => "GCEA",
                
"strings" => array(=> 7,  => 0,  => 4,  => 9)),
    
2  => array("name" => "DGBE (Baritone)",
                
"strings" => array(=> 2,  => 7,  => 11=> 4)),
    
10 => array("name" => "GDAE (Violin/mandolin)",
                
"strings" => array(=> 7,  => 2,  => 9,  => 4)),
    
11 => array("name" => "CGBD (Banjo)",
                
"strings" => array(=> 7,  => 2,  => 9,  => 4)),
    
20 => array("name" => "EADG (Bass Guitar)",
                
"strings" => array(=> 4,  => 9,  => 2,  => 7)),
    
21 => array("name" => "EADGBE (Guitar)",
                
"strings" => array(=> 4,  => 9,  => 2,  => 7,  => 11,  => 4))
  );


// What does the user want to do?
if (isset($HTTP_GET_VARS["action"]) === FALSE)
  
$action "none";
else
  
$action $HTTP_GET_VARS["action"];

// Did the user supply a chord?
if (isset($HTTP_GET_VARS["chord"]))
  
$to_look_up $HTTP_GET_VARS["chord"];
else
  
$to_look_up "D";

if (isset(
$HTTP_GET_VARS["tuning"]))
{
  
// Did the user select a tuning in this request?

  
$tuning_in_use $HTTP_GET_VARS["tuning"];

  if (isset(
$tunings[$tuning_in_use]) === FALSE)
    
$tuning_in_use 0;
}
else if (isset(
$HTTP_COOKIE_VARS["tuning"]))
{
  
// ... or did we get one from a cookie?

  
$tuning_in_use $HTTP_COOKIE_VARS["tuning"];

  if (isset(
$tunings[$tuning_in_use]) === FALSE)
    
$tuning_in_use 0;
}
else
{
  
// No tuning chosen, fall back on the default

  
$tuning_in_use 0;
}

// Try to set a cookie for next time
setcookie("tuning"$tuning_in_usetime() + 60 60 24 356);

// Get the base notes of the strings in the current tuning
$base_notes $tunings[$tuning_in_use]["strings"];

// The number of strings this tuning/instrument has
$strings count($base_notes);



/*
  Picks $chord apart and returns the indices of the notes that it is 
  made up of.
*/
function split_chord($chord)
{
  global 
$base_notes;
  global 
$note_names;
  global 
$chord_types;

  
// Find out what type of chord we want
  
if (substr($chord, -3) == "dim")
    
$result $chord_types["dim"];
  elseif (
substr($chord, -6) == " major")
    
$result $chord_types[" major"];
  elseif (
substr($chord, -1) == "5")
    
$result $chord_types["5"];
  elseif (
substr($chord, -1) == "m")
    
$result $chord_types["m"];
  elseif (
substr($chord, -5) == "mmaj7")
    
$result $chord_types["mmaj7"];
  elseif (
substr($chord, -4) == "maj7")
    
$result $chord_types["maj7"];
  elseif (
substr($chord, -2) == "m7")
    
$result $chord_types["m7"];
  elseif (
substr($chord, -1) == "7")
    
$result $chord_types["7"];
  elseif (
substr($chord, -4) == "sus4")
    
$result $chord_types["sus4"];
  elseif (
substr($chord, -3) == "aug")
    
$result $chord_types["aug"];
  else 
// Assume major
    
$result $chord_types[""];

  
// Find the base note in the chord
  
$lookup array_flip($note_names);

  if (
substr($chord02) == "C#")
    
$chord_base $lookup["C#"];
  else if (
substr($chord02) == "D#")
    
$chord_base $lookup["D#"];
  else if (
substr($chord02) == "F#")
    
$chord_base $lookup["F#"];
  else if (
substr($chord02) == "G#")
    
$chord_base $lookup["G#"];
  else if (
substr($chord02) == "A#")
    
$chord_base $lookup["A#"];
  else if (
substr($chord01) == "C")
    
$chord_base $lookup["C"];
  else if (
substr($chord01) == "D")
    
$chord_base $lookup["D"];
  else if (
substr($chord01) == "E")
    
$chord_base $lookup["E"];
  else if (
substr($chord01) == "F")
    
$chord_base $lookup["F"];
  else if (
substr($chord01) == "G")
    
$chord_base $lookup["G"];
  else if (
substr($chord01) == "A")
    
$chord_base $lookup["A"];
  else if (
substr($chord01) == "B")
    
$chord_base $lookup["B"];
  else
    return;

  
// Offset each chord by the base note
  
foreach($result as $index => $note)
  {
    
$result[$index] = ($note $chord_base) % 12;
  }

  
// $result contains the indices of the notes found in the chord
  
return $result;
}



/*
  Displays an info page.
*/
function print_info()
{
?>

<p><strong>Welcome to the ukulele chord finder!</strong></p>

<p><strong>Note</strong>: This page uses a cookie to remember your preferred tuning until your next visit. If you are concerned about such an intrusion into your privacy, please turn cookies <strong>off</strong> in your browser.</p>

<p>This is version 3 of this utility. Version 1 was the original from 2005. Version 2 corrected some errors in the chord definitions. Version 3 added support for different tunings.</p>

<?php
}



/* Prints the list of available tunings. */
function tuning_list()
{
  global 
$tunings;
  global 
$action;
  global 
$tuning_in_use;
  global 
$to_look_up;

  
// The "tuning" <select> will also update the hidden
  // "tuning" field in the "display chord" (if JS is available)
  
?>

  <form method="get" action="ukulele.php"><p>Tuning:
  <select name="tuning" id="chosentuning" onchange="updateTuning()">

  <?php

  
// Print an option for each tuning, with its
  // index as value and name as text.

  
foreach($tunings as $tuning_index => $tuning)
  {  
    print 
"<option value=\"" $tuning_index "\"";

    if (
$tuning_in_use == $tuning_index)
      print 
" selected";

    print 
">";

    print 
$tuning["name"] . "</option>";
  }

  
?>
  </select> <input type="submit" value="Change">

  <input type="hidden" name="action" value="<?php

  
// Try to keep track of the previous action.
  // If the previous action was "find" - we won't try to preserve
  // any previous search result (the number of strings might have
  // changed, etc); we'll just present the search form again instead

  
if ($action == "find")
    print 
"searchform";
  else
    print 
$action;

  
?>" />

  <input type="hidden" name="chord" value="<?php print $to_look_up?>" />
  </p></form> 
  <?php
}



/*
  Prints the fretboard table for $chord. If $printable is true,
  it will instead be displayed as images.
*/
function print_chord($chord$printable)
{
  global 
$base_notes;
  global 
$note_names;
  global 
$strings;
  global 
$tuning_in_use;

  
$find_notes split_chord($chord);

  if (
is_array($find_notes) === FALSE)
  {
    print 
"<p>Unknown chord: $chord</p>";
    return;
  }

  
// Otherwise...

  // Determine the names of the notes that make up this chord
  
$temp = array();

  foreach(
$find_notes as $note)
    
array_push($temp$note_names[$note]);

  
$chord_components "(" implode($temp", ") . ")";

  
// Display the chord

  
if ($printable === TRUE)
  {
    
// Printer-friendly specific code

    
print "<h2 class=\"chord\">$chord</h2>";

    print 
"<p class=\"chordcomponents\">$chord_components</p>";

    print 
"<table class=\"compact\">";

    print 
"<tr>";

    for (
$string 1$string <= $strings$string++)
    {
      print 
"<td>";

      if (
in_array($base_notes[$string], $find_notes))
        print 
"<img width=\"24\" height=\"24\" src=\"pics/open.png\" />";
      else
        print 
"<img width=\"24\" height=\"24\" src=\"pics/top.png\" />";

      print 
"</td>";
    }

    print 
"</tr>";

    for (
$fret 1$fret <= 12$fret++)
    {
      print 
"<tr>";

      for (
$string 1$string <= $strings$string++)
      {
        
$note = ($base_notes[$string] + $fret) % 12;

        print 
"<td>";

        if (
in_array($note$find_notestrue))
          print 
"<img width=\"24\" height=\"24\" src=\"pics/finger.png\" />";
        else
          print 
"<img width=\"24\" height=\"24\" src=\"pics/fret.png\" />";

        print 
"</td>";
      }

      print 
"</tr>";
    }

    print 
"</table>";
  }
  else
  {
    
// Web-friendly tables

    
print "<table class=\"ukulele\">";

    print 
"<thead>";

    print 
"<tr>";

    
/*
      The leftmost tds need some local CSS overrides (background must be set to white
      and border explicitly removed) since IE is stupid and doesn't hide empty cells like
      it's supposed to.
    */
    
print "<td style=\"background-color: white; border: none;\"></td>";

    print 
"<th colspan=\"4\"><h2>$chord</h2>";

    print 
"<p><small>(<a href=\"ukulele.php?chord=" urlencode($chord) . 
          
"&action=displayprint" .
          
"&tuning=" $tuning_in_use "\">printer-friendly</a>)" .
          
"</small></p>";

    print 
"<p>$chord_components</p>";

    print 
"</th></tr>";
    print 
"<tr>";
    print 
"<td style=\"background-color: white; border: none;\"></td>";

    for (
$string 1$string <= $strings$string++)
    {
      if (
in_array($base_notes[$string], $find_notes))
        print(
"<td class=\"selection\">" $note_names[$base_notes[$string]] . "</td>");
      else
        print(
"<td>" $note_names[$base_notes[$string]] . "</td>");
    }

    print 
"</tr></thead>";

    print 
"<tbody>";

    for (
$fret 1$fret <= 12$fret++)
    {
      print 
"<tr>";

      print 
"<td class=\"fret\">" $fret "</td>";

      for (
$string 1$string <= $strings$string++)
      {
        
$note = ($base_notes[$string] + $fret) % 12;

        if (
in_array($note$find_notestrue))
          print 
"<td class=\"selection\">";
        else
          print 
"<td>";

        print 
$note_names[$note];

        print 
"</td>";
      }

      print 
"</tr>";
    }

    print 
"</tbody>";
    print 
"</table>";
  }

  return;
}



/*
  Search for chords containing the notes in $query.
*/
function find_chords($query)
{
  global 
$note_names;
  global 
$chord_types;
  global 
$base_notes;
  global 
$tuning_in_use;
  global 
$tunings;
  global 
$strings;

  
$result = array();

  
// Go through each base note
  
for ($base 0$base <= 11$base++)
  {
    
// Go through each chord type
    
foreach($chord_types as $chord_name => $chord_notes)
    {
      
// This will be set to false if any string doesn't match
      
$passed TRUE;

      
// Loop through each string
      
for ($string 1$string <= $strings$string++)
      {
        
$match FALSE;

    
// Does the user care about the value of this string?
        
if ($query[$string] == "any")
          
$match TRUE// No; pass by default
        
else
        {
          
// Check each possible note if it's possible
          
foreach($chord_notes as $offset)
          {
            if ((
$base $offset) % 12 == $query[$string] % 12)
              
$match TRUE;
          }
        }

        
// Did we NOT get a match for this string? If so, the whole chord failed.
        
if ($match === FALSE)
          
$passed FALSE;
      }

      
// Did every string pass in this chord? Add it to the result list!
      
if ($passed === TRUE)
        
array_push($result$note_names[$base] . $chord_name);
    }
  }

  
// Display result

  
if (count($result) == 0)
  {
    print 
"<p>No chords found.</p>";
  }
  else if (
count($result) == 1)
  {
    print 
"<p>Found one chord:</p>";
    
print_chord(array_pop($result), FALSE);
  }
  else
  {
    print 
"<p>Found " count($result) . " chords; candidates are:</p>";

    foreach(
$result as $chord)
      
print_chord($chordFALSE);
  }

  return;
}



if (
$action == "displayprint")
{
  
printHead("Chord: " $to_look_up);

  print 
"<body>";
  
print_chord($to_look_upTRUE);
  print 
"</body>";

  exit;
}



/*
  Insert the standard header. This also contains a small
  script for changing the tuning of a display query without
  having to reload.
*/

printHead("Ukulele Chord Finder",
          
"<script>function updateTuning() { document.getElementById(\"displaytuning\").value = document.getElementById(\"chosentuning\").value; }</script>");

?>
<body>
  <h1>Ukulele Chord Finder</h1>

  <p><a href="ukulele.php">Start</a> |
  <a href="ukulele.php?action=searchform&tuning=<?php print $tuning_in_use?>">Find chord</a></p>

  <?php tuning_list(); ?>

  <form method="get" action="ukulele.php">
  <p>
  <input type="hidden" name="action" value="display" />
  Display chord: <select name="chord">

  <?php
  
foreach($note_names as $note_name)
  {
    foreach(
$chord_types as $chord_name => $bah)
    {
      
$current_chord $note_name $chord_name;

      if (
$to_look_up === $current_chord)
        print 
"<option value=\"$current_chord\" selected>";
      else
        print 
"<option value=\"$current_chord\">";

      print 
$current_chord "</option>";
    }
  }
  
?>
  </select>
  
  <input type="hidden" id="displaytuning" name="tuning" value="<?php print $tuning_in_use?>" />
  <input type="submit" value="Display" />
  </p>
  </form>


  <?php
  
if ($action == "display")
  {
    
print_chord($to_look_upFALSE);
  }
  else if (
$action == "none")
  {
    
print_info();
  }
  else if (
$action == "find")
  {
    if (isset(
$HTTP_GET_VARS["string"]))
      
find_chords($HTTP_GET_VARS["string"]);
    else
      print 
"Error: Insufficient query data.";
  }
  else if (
$action == "searchform")
  {
  
?>
  <h2>Find Chord</h2>

  <form>
  <input type="hidden" name="action" value="find" />
  <input type="hidden" name="tuning" value="<?php print $tuning_in_use?>" />

  <table class="ukulele">
  <thead>
  <tr>
  <td style="width: auto; background-color: white; border: none;"></td>
  <?php
  
for ($string 1$string <= $strings$string++)
  {
    print 
"<td style=\"width: 60px;\">";
    print 
$note_names[$base_notes[$string]];
    print 
"</td>";
  }
  
?>

  </tr>
  </thead>
  <tbody>
  <tr>
  <td style="width: auto; background-color: white; border: none;"></td>

  <?php
  
for ($string 1$string <= $strings$string++)
  {
    print 
"<td>";
    print 
"<input type=\"radio\" name=\"string[" $string "]\" value=\"any\" checked>";
    print 
"any</td>";
  }
  
?>
  
  </tr>
  <tr>
  <td style="width: auto; background-color: white; border: none;"></td>

  <?php
  
for ($string 1$string <= $strings$string++)
  {
    print 
"<td>";
    print 
"<input type=\"radio\" name=\"string[" .
          
$string "]\" value=\"" $base_notes[$string] . "\">";
    print 
"open</td>";
  }
  
?>
  </tr>

  <?php
  
for ($fret 1$fret <= 12$fret++)
  {
    print 
"<tr>";
    print 
"<td class=\"fret\">" $fret "</td>";

    for (
$string 1$string <= $strings$string++)
    {
      
$note = ($base_notes[$string] + $fret) % 12;

      print 
"<td>";
      print 
"<input type=\"radio\" name=\"string[" $string "]\" value=\"" $note "\">";
      print 
$note_names[$note];
      print 
"</td>";
    }

    print 
"</tr>";
  }
  
?>
  </tbody>
  <table>

  <input style="margin-left: 35px" type="submit" value="Find" />
  </form>

  <?php
  
/* searchform */
  
?>

  <?php @printPageFoot(); ?>
</body>
</html>