Full Code of makaroni4/guitar_bro for AI

master 334ea0450a5a cached
14 files
46.7 KB
14.7k tokens
21 symbols
1 requests
Download .txt
Repository: makaroni4/guitar_bro
Branch: master
Commit: 334ea0450a5a
Files: 14
Total size: 46.7 KB

Directory structure:
gitextract_95i3534h/

├── LICENSE
├── README.md
├── css/
│   └── app.css
├── index.html
└── js/
    ├── app.js
    ├── audio_processor.js
    ├── audio_wave.js
    ├── config.js
    ├── explosion_effect.js
    ├── fretboard.js
    ├── health_drawer.js
    ├── helper_functions.js
    ├── sharing.js
    └── song_loader.js

================================================
FILE CONTENTS
================================================

================================================
FILE: LICENSE
================================================
MIT License

Copyright (c) 2017 Anatoli Makarevich

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: README.md
================================================
# Guitar Bro – browser game that helps you learn notes on guitar

![Guitar Bro Snapshot](https://user-images.githubusercontent.com/768070/27518743-17a23ec4-59e7-11e7-8873-5b8ee3be5251.png)

## Description

Guitar Bro works completely in browser and is based on [Web Audio API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API). Currently Guitar Bro works only in Chrome, since only Chrome allows to change the [resolution of FFT](https://developer.mozilla.org/en-US/docs/Web/API/AnalyserNode/fftSize) to distinguish different notes on guitar.

[Try it out!](https://makaroni4.github.io/guitar_bro/)


================================================
FILE: css/app.css
================================================
html, body {
  width:  100%;
  height: 100%;
  margin: 0;
  padding: 0;
  overflow: hidden;
}

html {
  box-sizing: border-box;
}

*, *:before, *:after {
  box-sizing: inherit;
}

.allow-mic {
  display: none;

  background-color: transparent;
}

.allow-mic--active {
  display: block;
}

.allow-mic__arrow {
  position: absolute;
  left: 408px;
  top: 60px;
  width: 60px;
  height: 72px;

  background-image: url("../img/allow_mic_arrow.png");
  background-size: cover;
}

.allow-mic__message {
  position: absolute;
  width: 250px;
  left: 330px;
  top: 140px;

  font-family: 'Kalam', cursive;
  font-size: 24px;
  color: #F1FAEE;
}

.header {
  display: flex;
  align-items: center;
  height: 60px;
}

.header__audio-wave {
  position: absolute;
  top: 2px;
  left: 10px;
}

.header__score {
  display: none;
  position: absolute;
  top: 20px;
  right: 40px;

  font-size: 32px;
  line-height: 48px;
  font-weight: bold;
  color: #A8DADC;
}

.real-guitar-hero {
  background-color: #1D3557;
}

.real-guitar-hero__header {
  position: absolute;
}

.real-guitar-hero__canvas {
}

.real-guitar-hero__settings {
  display: block;
  position: absolute;
  top: 10px;
  right: 10px;

  font-family: 'Source Sans Pro', sans-serif;
  font-weight: bold;
  color: #F1FAEE;
  text-decoration: none;
}

.game-settings {
  width: 100%;
}


.game-settings__bpm-input-label {
  display: block;

  font-family: 'Source Sans Pro', sans-serif;
  font-weight: bold;
  color: #1D3557;
  text-decoration: none;
}

.game-settings__bpm-input {
  display: block;
  padding: 8px;
  width: 100%;

  font-size: 24px;
  line-height: 32px;
}

.game-settings__song-select-label,
.game-settings__string-select-label,
.game-settings__mode-select-label {
  display: block;
  margin-top: 16px;

  font-family: 'Source Sans Pro', sans-serif;
  font-weight: bold;
  color: #1D3557;
  text-decoration: none;
}

.game-settings__song-select,
.game-settings__string-select {
  display: block;
  width: 100%;
  height: 52px;

  background-color: #FFF;

  font-family: 'Source Sans Pro', sans-serif;
  color: #1D3557;
  text-decoration: none;
  font-size: 18px;
  line-height: 24px;
}

.game-settings__mode-select {
  font-family: 'Source Sans Pro', sans-serif;
  color: #1D3557;
  text-decoration: none;
  font-size: 18px;
  line-height: 24px;
}

.game-settings__mode-select-hint {
  font-size: 14px;
  line-height: 14px;
}

.game-settings__mode-select-item + .game-settings__mode-select-item {
  margin-top: 10px;
}

.real-guitar-hero__score {
  font-size: 24px;
  color: #0654C8;
}

.sidebar-menu {
  display: none;
  flex-direction: column;
  justify-content: space-between;
  padding: 16px 20px;
  position: absolute;
  width: 400px;
  height: 100%;
  top: 0;
  right: 0;
  z-index: 100;

  background-color: #E4EEFB;
}

.sidebar-menu--active {
  display: flex;
}

.sidebar-menu__footer {
  text-align: right;
}

.footer-link {
  font-family: 'Source Sans Pro', sans-serif;
  font-size: 16px;
  line-height: 20px;
  color: #1D3557;
  text-decoration: none;
}

.sidebar-menu__footer a + a {
  margin-left: 15px;
}

.sidebar-menu__header {
  margin: 0;
  padding: 0;

  font-family: 'Source Sans Pro', sans-serif;
  font-size: 24px;
  line-height: 32px;
  color: #1D3557;
}

.sidebar-menu__subheader {
  margin-top: 20px;

  font-family: 'Source Sans Pro', sans-serif;
  font-size: 16px;
  line-height: 20px;
  color: #1D3557;
}

.sidebar-menu__settings {
  margin-top: 20px;
}

.sidebar-menu__start-button {
  margin: 50px auto 0;
  display: block;
  width: 350px;
  padding: 20px 25px;

  border: none;
  border-radius: 2px;

  background-color: #7C69F4;

  font-family: 'Source Sans Pro', sans-serif;
  font-weight: bold;
  font-size: 24px;
  line-height: 32px;
  color: #FFF;
  letter-spacing: 4px;

  outline: none;
  cursor: pointer;
}

.sidebar-menu__start-button:hover {
  opacity: 0.9;
}

.sidebar-menu__install-chrome,
.sidebar-menu__update-chrome {
  display: none;
  margin-top: 10px;

  font-family: 'Source Sans Pro', sans-serif;
  font-size: 16px;
  line-height: 20px;
  color: #1D3557;
}

.sidebar-menu__install-chrome--active,
.sidebar-menu__update-chrome--active {
  display: block;
}

.sidebar-menu__install-chrome i,
.sidebar-menu__update-chrome i {
  color: red;
}

.sidebar-menu__install-chrome a,
.sidebar-menu__update-chrome a {
  font-weight: bold;
  text-decoration: none;
}

.sidebar-menu__install-chrome a:hover,
.sidebar-menu__update-chrome a:hover {
  text-decoration: underline;
}

.sidebar-menu__sharing-buttons {
  display: flex;
  margin-top: 20px;
  align-items: center;
  justify-content: center;
}

.sharing-buttons {
  display: flex;
  margin-bottom: 20px;
}

.share-button {
  display: inline-block;
  padding: 10px 15px;

  border-radius: 2px;

  font-family: 'Source Sans Pro', sans-serif;
  font-size: 16px;
  line-height: 20px;
  color: #F1FAEE;
  text-decoration: none;
}

.share-button:hover {
  opacity: 0.9;
}

.share-button i {
  margin-right: 4px;
}

.share-button + .share-button {
  margin-left: 8px;
}

.share-button--fb {
  background-color: #3b5998;
}

.share-button--tw {
  background-color: #4099FF;
}

.audio-wave {
  display: none;

  width: 60px;
  height: 32px;
}

.audio-wave--active {
  display: block;
}

.audio-wave path {
  stroke: #F1FAEE;
  stroke-width: 1;
  fill: none;
  opacity: 0.5;
}

.no-sound {
  display: none;
  padding-top: 8px;

  color: white;
  font-size: 30px;
}

.no-sound--active {
  display: block;
}

.github-corner:hover .octo-arm{animation:octocat-wave 560ms ease-in-out}@keyframes octocat-wave{0%,100%{transform:rotate(0)}20%,60%{transform:rotate(-25deg)}40%,80%{transform:rotate(10deg)}}@media (max-width:500px){.github-corner:hover .octo-arm{animation:none}.github-corner .octo-arm{animation:octocat-wave 560ms ease-in-out}}


================================================
FILE: index.html
================================================
<!DOCTYPE html>
<html>
<head>
  <title>Guitar Bro – browser game with a real guitar.</title>

  <meta property="og:url"           content="https://makaroni4.github.io/guitar_bro/" />
  <meta property="og:type"          content="website" />
  <meta property="og:title"         content="Guitar Bro – open source browser game that helps you learn notes on guitar" />
  <meta property="og:description"   content="Practice and learn notes on any string – just grab your guitar, select comfortable rythm and play" />
  <meta property="og:image"         content="https://makaroni4.github.io/guitar_bro/img/og.png" />
  <meta property="fb:app_id"        content="1611939258817451">

  <meta name="twitter:card" content="https://makaroni4.github.io/guitar_bro/img/og.png">
  <meta name="twitter:site" content="@makaroni4">
  <meta name="twitter:title" content="Guitar Bro – open source browser game that helps you learn notes on your guitar">
  <meta name="twitter:description" content="Practice and learn notes on any string – just grab your guitar, select comfortable rythm and play">
  <meta name="twitter:creator" content="@makaroni4">
  <meta name="twitter:image" content="https://makaroni4.github.io/guitar_bro/img/og.png">
  <meta name="twitter:domain" content="makaroni4.com">

  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">

  <link rel="stylesheet" href="css/app.css">
  <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css">

  <link href="https://fonts.googleapis.com/css?family=Kalam:700" rel="stylesheet">
  <link href="https://fonts.googleapis.com/css?family=Source+Sans+Pro:400,700" rel="stylesheet">
</head>

<body>
  <div class="allow-mic">
    <div class="allow-mic__arrow">
    </div>

    <div class="allow-mic__message">
      Allow microphone so we can recognize musical notes when you're playing
    </div>
  </div>

  <div class="real-guitar-hero">
    <div class="real-guitar-hero__header">
      <header class="header">
        <div class="header__audio-wave">
          <div class="audio-wave audio-wave--active">
          </div>

          <div class="no-sound">
            <i class="fa fa-microphone-slash" aria-hidden="true"></i>
          </div>
        </div>

        <div class="header__score score">
          Score: <span class="score__points">0</span>
        </div>
      </header>
    </div>

    <div class="real-guitar-hero__canvas">
      <canvas id="game-canvas"></canvas>
    </div>
  </div>

  <div class="sidebar-menu sidebar-menu--active">
    <div class="sidebar-menu__body">
      <h1 class="sidebar-menu__header" data-welcome-copy="Welcome to Guitar Bro – guitar browser game with a real guitar!" data-game-over-copy="Game over! Did you have some fun?">
        Welcome to Guitar Bro – guitar browser game with a real guitar!
      </h1>

      <div class="sidebar-menu__subheader" data-welcome-copy="Pick up a string you want to play on" data-game-over-copy="Game over! Nice one! Try to adjust BPM and try again!">
        Pick up a string you want to play on (yep, it's "unitar").
      </div>

      <div class="sidebar-menu__settings">
        <div class="game-settings">
          <label class="game-settings__bpm-input-label">
            BPM
            <input class="game-settings__bpm-input" type="number" value="30" />
          </label>

          <label class="game-settings__song-select-label">
            Select a song
            <select class="game-settings__song-select">
            </select>
          </label>

          <label class="game-settings__string-select-label">
            Select a string
            <select class="game-settings__string-select">
            </select>
          </label>

          <div class="game-settings__mode-select">
            <div class="game-settings__mode-select-label">
              Select a mode
            </div>

            <div class="game-settings__mode-select-item">
              <input type="radio" id="game-settings-survival-mode" name="game-mode-select" value="survival" checked>
              <label for="game-settings-survival-mode" class="game-settings__mode-select-input">
                Survival
                <div class="game-settings__mode-select-hint">You start with 5 health (and get 1 health on correct note)</div>
              </label>
            </div>

            <div class="game-settings__mode-select-item">
              <input type="radio" id="game-settings-sandbox-mode" name="game-mode-select" value="sandbox" class="game-settings__mode-select-input">
              <label for="game-settings-sandbox-mode" class="game-settings__mode-select-input">
                Sandbox
                <div class="game-settings__mode-select-hint">The game will stop when you stop it</div>
              </label>
            </div>
          </div>
        </div>
      </div>

      <button class="sidebar-menu__start-button js-start">
        START NEW GAME
      </button>

      <div class="sidebar-menu__install-chrome">
        <i class="fa fa-exclamation-triangle" aria-hidden="true"></i> To enjoy Guitar Bro you need to open this web page in <a href="https://www.google.com/chrome/browser/desktop/">Google Chrome</a>. It's the only browser now that supports sound analysis on the client (yep, there is no fancy servers – all magic happens here).
      </div>

      <div class="sidebar-menu__update-chrome">
        <i class="fa fa-exclamation-triangle" aria-hidden="true"></i> To enjoy Guitar Bro you need to update you Chrome browser to the latest version. Open <a href="chrome://help/">Chrome settings</a> and make sure you have version 58 or more (latest version supports sound analysis, Guitar Bro works completely in browser).
      </div>
    </div>

    <div class="sidebar-menu__footer">
      <div class="sidebar-menu__sharing-buttons">
        <div class="sharing-buttons">
          <a href="https://www.facebook.com/sharer.php?u=https://makaroni4.github.io/guitar_bro/" class="share-button share-button--fb" data-event-label="fb">
            <i class="fa fa-facebook" aria-hidden="true"></i>
            Share on Facebook
          </a>
          <a href="https://twitter.com/share?url=https://makaroni4.github.io/guitar_bro/&amp;text=Try Guitar Bro – open source browser game to learn notes on guitar" class="share-button share-button--tw" data-event-label="tw">
            <i class="fa fa-twitter" aria-hidden="true"></i>
            Share on Twitter
          </a>
        </div>
      </div>

      <a class="footer-link" href="https://makaroni4.github.io/triads/" target="_blank">
        <i class="fa fa-hand-spock-o" aria-hidden="true"></i>
        Learn triads
      </a>

      <a class="footer-link" href="http://makaroni4.com/2017/07/10/guitar-bro/" target="_blank">
        <i class="fa fa-cogs" aria-hidden="true"></i>
        How it works?
      </a>

      <a class="footer-link" href="https://github.com/makaroni4/real_guitar_hero" target="_blank">
        <i class="fa fa-github" aria-hidden="true"></i>
        Source Code
      </a>
    </div>
  </div>

  <script>
    (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
    (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
    m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
    })(window,document,'script','https://www.google-analytics.com/analytics.js','ga');

    ga('create', 'UA-101505240-1', 'auto');
    ga('send', 'pageview');
  </script>

  <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.9.1/d3.min.js"></script>
  <script src="js/config.js" type="text/javascript" charset="utf-8" async defer></script>
  <script src="js/song_loader.js" type="text/javascript" charset="utf-8" async defer></script>
  <script src="js/health_drawer.js" type="text/javascript" charset="utf-8" async defer></script>
  <script src="js/helper_functions.js" type="text/javascript" charset="utf-8" async defer></script>
  <script src="js/explosion_effect.js" type="text/javascript" charset="utf-8" async defer></script>
  <script src="js/fretboard.js" type="text/javascript" charset="utf-8" async defer></script>
  <script src="js/audio_wave.js" type="text/javascript" charset="utf-8" async defer></script>
  <script src="js/audio_processor.js" type="text/javascript" charset="utf-8" async defer></script>
  <script src="js/app.js" type="text/javascript" charset="utf-8" async defer></script>
  <script src="js/sharing.js" type="text/javascript" charset="utf-8" async defer></script>
</body>
</html>


================================================
FILE: js/app.js
================================================
$(function() {
  var $game = $(".real-guitar-hero"),
      $score = $game.find(".score__points");

  var $settings = $(".game-settings"),
      $bpmInput = $settings.find(".game-settings__bpm-input"),
      $songSelect = $settings.find(".game-settings__song-select"),
      $stringSelect = $settings.find(".game-settings__string-select"),
      $modeSelect = $settings.find("input:radio[name=game-mode-select]"),
      isSandboxMode = $modeSelect.val() === "sandbox",
      isSurvivalMode = !isSandboxMode;

  for(string in gameConfig.strings) {
    var $option = $("<option/>");
    $option.val(string);
    $option.text(gameConfig.strings[string].name);

    if(string === "1") {
      $option.attr("selected", "selected");
    }

    $stringSelect.append($option);
  }

  var $sidebarMenu = $(".sidebar-menu"),
      $startButton = $sidebarMenu.find(".js-start");

  var songIndex, stringIndex, bpm, fretboard;

  // song loader
  var songLoader = new SongLoader();

  // fps options
  var fpsInterval = 1000 / gameConfig.fps,
      startTime,
      now,
      then,
      elapsed;

  var gameIsOn = false;

  //canvas variables
  var canvas = document.getElementById("game-canvas");

  var ctx = canvas.getContext("2d");
  ctx.canvas.width = window.innerWidth;
  ctx.canvas.height = window.innerHeight;

  var explosion = new ExplosionEffect(ctx);

  var healthDrawer = new HealthDrawer(ctx);

  // game variables
  var continueAnimating = false,
      score = 0,
      correctNotes = 0,
      MAX_HEALTH = 5,
      health = MAX_HEALTH;

  // block variables
  var pegWidth = 1;

  // rock variables
  var rockWidth = canvas.width / 12;
  var rockFontSize = 0.5 * rockWidth;
  var rockSpeed;
  var rockHeight = rockWidth;
  var eightsDurationDistance = rockHeight;
  var rocks = [];

  function initRocks(songIndex, string) {
    rocks = [];
    var song = songLoader.loadSong(songIndex, string);
    var totalRocks = song.length;

    for (var i = 0; i < totalRocks; i++) {
      addRock(i, song);
    }
  }

  function calculateRockY(rockIndex) {
    var prevRock = rockIndex === 0 ? rocks[rocks.length - 1] : rocks[rockIndex - 1];
    var minRockY = rocks.length === 0 ? 0 : Math.min.apply(Math, rocks.map(function(r){return r.y;}));

    return rocks.length === 0 ? 0 : minRockY - prevRock.durationDistance;
  }

  function addRock(rockIndex, song) {
    var rock = {
      width: rockWidth - pegWidth,
      height: rockHeight,
      durationDistance: eightsDurationDistance * 8 / song[rockIndex][1]
    }

    var prevRock = rockIndex === 0 ? rocks[rocks.length - 1] : rocks[rockIndex - 1];

    rock.note = song[rockIndex][0];

    var noteIndex = songLoader.findNoteIndex(rock.note, stringIndex);

    rock.x = noteIndex * rockWidth + pegWidth;
    rock.y = calculateRockY(rockIndex);

    rocks.push(rock);
  }

  function toggleMenuCopy(type) {
    var $header = $sidebarMenu.find(".sidebar-menu__header"),
        $subheader = $sidebarMenu.find(".sidebar-menu__subheader");

    $header.text($header.data(type));
    $subheader.text($subheader.data(type));
  }

  function stopGame() {
    ga("send", {
      hitType: "event",
      eventCategory: "Game",
      eventAction: "Lost",
      eventValue: correctNotes
    });

    toggleSettings();

    $sidebarMenu.addClass("sidebar-menu--active");
    toggleMenuCopy("gameOverCopy");

    $songSelect.val(songIndex);
    $stringSelect.val(stringIndex);
    $bpmInput.val(bpm);

    gameIsOn = false;
  }

  function animate() {
    if(health === 0 && !continueAnimating) {
      clearBackground();
    }

    if(continueAnimating) {
      requestAnimationFrame(animate);
    } else {
      return;
    }

    now = Date.now();
    elapsed = now - then;

    if (elapsed > fpsInterval) {
      // Get ready for next frame by setting then=now, but also adjust for your
      // specified fpsInterval not being a multiple of RAF's interval (16.7ms)
      then = now - (elapsed % fpsInterval);

      // Drawing code
      for (var i = 0; i < rocks.length; i++) {
        var rock = rocks[i];

        rock.y += rockSpeed;

        if (rock.y > canvas.height) {
          if(rock.highlightColor) {
            explosion.add(rock.x + rock.width / 2, canvas.height - 5, rock.highlightColor === gameConfig.colors.green);
          } else {
            decrementScore();
          }

          rock.y = calculateRockY(i);
          rock.highlightColor = undefined;

          if(health === 0 && isSurvivalMode) {
            stopGame();
          }
        }
      }

      drawAll();
    }
  }

  function isColliding(rock) {
    return rock.y > canvas.height - rockHeight;
  }

  function decrementScore() {
    score -= 10;
    health -= 1;
  }

  function clearBackground() {
    // clear the canvas
    ctx.clearRect(0, 0, canvas.width, canvas.height);

    // draw the background
    ctx.fillStyle = gameConfig.colors.dark_blue;
    ctx.fillRect(0, 0, canvas.width, canvas.height);
  }

  function drawAll() {
    clearBackground();

    fretboard.draw();

    // draw all rocks
    for (var i = 0; i < rocks.length; i++) {
      var rock = rocks[i];

      if(rock.y + rock.height > 0) {
        ctx.font = "bold " + rockFontSize + "px Source Sans Pro, sans-serif";

        var lineWidth = 8;
        ctx.lineWidth = lineWidth;
        ctx.strokeStyle = rock.highlightColor ? rock.highlightColor : gameConfig.colors.white;
        ctx.textAlign="center";
        ctx.textBaseline = "middle";

        var textString = rock.note,
            textWidth = ctx.measureText(textString).width;

        ctx.beginPath();
        ctx.arc(rock.x + rockWidth / 2 - pegWidth / 2, rock.y + rockHeight / 2, rock.width / 2 - lineWidth / 2, 0, 2 * Math.PI);
        ctx.stroke();
        ctx.fillStyle = gameConfig.colors.dark_blue;
        ctx.fill();

        ctx.fillStyle = gameConfig.colors.white;
        ctx.fillText(textString, rock.x + rockWidth / 2, rock.y + rockHeight / 2);
      }

      ctx.lineWidth = 1;
    }

    healthDrawer.draw(health, isSandboxMode);

    $score.text(score);

    explosion.draw();
  }

  songLoader.populateSelectMenu($songSelect);

  songIndex = $songSelect.val();
  stringIndex = $stringSelect.val();
  bpm = $bpmInput.val();

  if(getChromeVersion() > 57) {
    var processor = new AudioProcessor();

    processor.setString(stringIndex);
    processor.attached();

    $(".allow-mic").addClass("allow-mic--active");
  } else if(getChromeVersion() < 58) {
    $(".sidebar-menu__update-chrome").addClass("sidebar-menu__update-chrome--active");
  } else {
    $(".sidebar-menu__install-chrome").addClass("sidebar-menu__install-chrome--active");

    showNoMic();
  }

  function showNoMic() {
    $(".no-sound").addClass("no-sound--active");
    $(".audio-wave").removeClass("audio-wave--active");
  }

  $(document).on("no_mic", function() {
    showNoMic();
  });

  $(document).on("note_detected", function(event, note) {
    if(!gameIsOn) {
      return;
    }

    var rockIndex = rocks.findIndex(function(r) {
      return r.y >= canvas.height - 2 * rockHeight;
    });

    if(rockIndex === -1) {
      fretboard.highlightFret(note);
      return;
    }

    var rock = rocks[rockIndex];

    if(!isColliding(rock)) {
      fretboard.highlightFret(note);
      return;
    }

    if(rock.highlightColor) {
      return;
    }

    var correctAnswer = note === rock.note;

    fretboard.highlightFret(note, correctAnswer ? gameConfig.colors.green : gameConfig.colors.red);

    if(correctAnswer) {
      score += 10;
      correctNotes += 1;

      if(health < MAX_HEALTH) {
        health += 1;
      }
    } else {
      decrementScore();
    }

    explosion.add(rock.x, rock.y, correctAnswer);

    rock.highlightColor = correctAnswer ? gameConfig.colors.green : gameConfig.colors.red;
  });

  $startButton.on("click", function () {
    ga("send", "event", "Game", "Start", isSandboxMode ? "sandbox" : "survival");

    fretboard = new Fretboard(canvas, songLoader, stringIndex, rockWidth, pegWidth);

    toggleMenuCopy("welcomeCopy");
    $(".allow-mic").removeClass("allow-mic--active");
    $sidebarMenu.removeClass("sidebar-menu--active");

    correctNotes = 0;

    var beatDuration = 60 / bpm;

    rockSpeed = eightsDurationDistance * 8 / (gameConfig.fps * beatDuration);

    then = Date.now();
    startTime = then;
    continueAnimating = !continueAnimating;

    if(continueAnimating) {
      gameIsOn = true;
      score = 0;
      health = MAX_HEALTH;
      initRocks(songIndex, stringIndex);

      if(getChromeVersion()) {
        processor.setString(stringIndex);
      }
    }

    animate();
  });

  var toggleSettings = function(params) {
    $sidebarMenu.toggleClass("sidebar-menu--active");

    continueAnimating = !$sidebarMenu.hasClass("sidebar-menu--active");

    gameIsOn = continueAnimating;

    animate();
  }

  $songSelect.on("change", function(e) {
    songIndex = $(this).val();
  });

  $stringSelect.on("change", function(e) {
    stringIndex = $(this).val();
  });

  $bpmInput.on("change", function(e) {
    bpm = $(this).val();
  });

  $modeSelect.on("change", function(e) {
    isSandboxMode = $(this).val() === "sandbox",
    isSurvivalMode = !isSandboxMode;
  })

  $(document).on("keydown", function(e) {
    if(e.keyCode === 32 && elapsed) {
      toggleSettings();
    }
  });
});


================================================
FILE: js/audio_processor.js
================================================
// https://github.com/GoogleChrome/guitar-tuner
function AudioProcessor() {
  this.FFTSIZE = 2048 * 4;
  this.stream = null;
  this.audioContext = new AudioContext();
  this.analyser = this.audioContext.createAnalyser();
  this.gainNode = this.audioContext.createGain();
  this.microphone = null;

  this.gainNode.gain.value = 0;
  this.analyser.fftSize = this.FFTSIZE;
  this.analyser.smoothingTimeConstant = 0.1;

  this.frequencyBufferLength = this.FFTSIZE;
  this.frequencyBuffer = new Float32Array(this.frequencyBufferLength / 2);
  this.timeBuffer = new Float32Array(this.frequencyBufferLength);

  this.sendingAudioData = false;

  this.lastNoteEnergy = 0;
  this.wave_power_threshold = 0.006;
  this.last_note_time = -1;

  var audioWaveChart = new AudioWaveChart();

  var string;
  var that = this;

  that.requestUserMedia = function () {
    navigator.getUserMedia({audio: true}, (stream) => {
      that.sendingAudioData = true;
      that.stream = stream;
      that.microphone = that.audioContext.createMediaStreamSource(stream);
      that.microphone.connect(that.analyser);
      that.analyser.connect(that.gainNode);
      that.gainNode.connect(that.audioContext.destination);

      requestAnimationFrame(that.dispatchAudioData);

      ga("send", "event", "Game", "MicEnabled");

      $(".allow-mic").removeClass("allow-mic--active");
    }, (err) => {
      ga("send", "event", "Game", "MicDisabled");

      $(document).trigger("no_mic");

      $(".allow-mic").removeClass("allow-mic--active");

      console.log('Unable to access the microphone');
      console.log(err);
    });
  }

  this.attached = function() {
    // Set up the stream kill / setup code for visibility changes.
    document.addEventListener('visibilitychange', this.onVisibilityChange);

    // Then call it.
    this.onVisibilityChange();
  }

  this.detached = function() {
    this.sendingAudioData = false;
  }

  this.onVisibilityChange = function() {
    if (document.hidden) {
      that.sendingAudioData = false;

      if (that.stream) {
        // Chrome 47+
        that.stream.getAudioTracks().forEach((track) => {
          if ('stop' in track) {
            track.stop();
          }
        });

        // Chrome 46-
        if ('stop' in that.stream) {
          that.stream.stop();
        }
      }

      that.stream = null;
    } else {
      that.requestUserMedia();
    }

  }

  this.setString = function(string_num){
    string = gameConfig.strings[string_num];
  }

  this.findNoteFreq = function(time) {
    let freq_step = that.audioContext.sampleRate / this.FFTSIZE;
    let min_freq_ind = Math.round(string.range[0] / freq_step);
    let max_freq_ind = Math.round(string.range[1] / freq_step);

    // Fill up the data.

    that.analyser.getFloatTimeDomainData(that.timeBuffer);
    that.analyser.getFloatFrequencyData(that.frequencyBuffer);
    freq = that.frequencyBuffer;
    wave = that.timeBuffer;

    audioWaveChart.plotWave(wave);

    for (let d = Math.round(Math.max(min_freq_ind - 20 / freq_step - 5, 0)); d < Math.min(max_freq_ind + 20 / freq_step + 5, freq.length); d++) {
      freq[d] = Math.pow(10, 5 + freq[d] / 10);
    }


    let max_A = -1000000;
    let arg_max = -1;
    for (let i = min_freq_ind; i < max_freq_ind; i++) {
      if (freq[i] > max_A){
        max_A = freq[i];
        arg_max = i;
      }
    }

    let total_energy = 0;
    for (let i = min_freq_ind; i < max_freq_ind; i++) {
      total_energy += freq[i];
    }

    let maximum_energy = 0;
    for (let i = Math.round(arg_max - 20 / freq_step - 1); i <= Math.round(arg_max + 20 / freq_step + 1); i++){
      maximum_energy += freq[i];
    }

    if (maximum_energy < 0.1 || maximum_energy / total_energy < 0.96){
      return -1;
    }

    if (time > this.last_note_time + 100){
      this.lastNoteEnergy = 0;
    }

    // if (maximum_energy < this.lastNoteEnergy){
    //   return -1;
    // }
    // console.log(arg_max * freq_step, maximum_energy);

    this.last_note_time = time;
    this.lastNoteEnergy = maximum_energy;

    return arg_max * freq_step;
  }


  this.dispatchAudioData = function(time) {

    if (that.sendingAudioData) {
      requestAnimationFrame(that.dispatchAudioData);
    }

    let frequency = that.findNoteFreq(time);
    if (frequency < 0){
      return;
    }

    let freqs = string.freqs;

    let min_freq_error = 10000;
    let best_chord_ind = 0;
    for (let i = 0; i < freqs.length; i++){
      let chord_freq = freqs[i][0];
      let chord = freqs[i][1];

      //let n_div = frequency / chord_freq;   //for future
      let n_div = 1;
      let error = Math.abs( Math.round(n_div) * chord_freq - frequency);
      if (error < min_freq_error){
        best_chord_ind = i;
        min_freq_error = error;
      }
    }

    if (min_freq_error < 20){
      $(document).trigger("note_detected", freqs[best_chord_ind][1]);
    }

  }
}


================================================
FILE: js/audio_wave.js
================================================
function AudioWaveChart() {
    var $audioWave = $(".audio-wave");

    var w = $audioWave.width();
    var h = $audioWave.height();

    var x_scale = d3.scaleLinear().range([0, w]).domain([0, 1]);
    var y_scale = d3.scaleLinear().range([h, 0]).domain([0, 1]);

    var line = d3.line()
            .x(function(d) {
                return x_scale(d.x);})
            .y(function(d) {
                return y_scale(d.y);
            })

    var graph = d3.select(".audio-wave").append("svg:svg")
                  .attr("width", w)
                  .attr("height", h)
                  .append("svg:g");

    graph.append("svg:path").attr("class", "line");

    var setDomain = function(data_xy){
        x_scale.domain(d3.extent(data_xy, function(d){ return d.x}));

        var y_range = d3.extent(data_xy, function(d){ return d.y});

        y_scale.domain([ Math.min(-0.1, y_range[0]), Math.max(0.1, y_range[1]) ]);
    };

    var plotD3Wave = function(data_xy) {
        setDomain(data_xy);
        var svg = d3.select("body").transition();
        svg.select(".line")
            .duration(0)
            .attr("d", line(data_xy));
    }

    return {
        plotWave: function(wave) {
            let found_good_ind = 0;
            for (let i = 0; i < wave.length - 1; i++){
              if (wave[i] < 0 && wave[i + 1] >= 0){
                found_good_ind = i;
                break;
              }
            }
            found_good_ind = Math.min(found_good_ind, wave.length - 500);

            wave_short = wave.slice(found_good_ind, found_good_ind + 500);
            data_xy  = [];
            for (let i = 0; i < wave_short.length; i++){
              data_xy.push({x: i, y: wave_short[i]});
            }

            plotD3Wave(data_xy);
        }
    }
}


================================================
FILE: js/config.js
================================================
var gameConfig = {
  fps: 50,
  colors: {
    green: "#9BC53D",
    yellow: "#FDE74C",
    red: "#E55934",
    white: "#F1FAEE",
    dark_blue: "#1D3557"
  },
  strings: {
    1: {
      name: "1. E-string (thinnest)",
      range: [325, 665],
      freqs: [
        [329.6, "E"],
        [349.2, "F"],
        [370.0, "F#"],
        [392.0, "G"],
        [415.3, "G#"],
        [440.0, "A"],
        [466.1, "A#"],
        [493.8, "B"],
        [523.2, "C"],
        [554.3, "C#"],
        [587.3, "D"],
        [622.2, "D#"],
        [659.2, "E"],
      ]
    },
    2: {
      name: "2. B-string",
      range: [242, 499],
      freqs: [
        [246.9, "B"],
        [261.6, "C"],
        [277.2, "C#"],
        [293.7, "D"],
        [311.1, "D#"],
        [329.6, "E"],
        [349.2, "F"],
        [370.0, "F#"],
        [392.0, "G"],
        [415.3, "G#"],
        [440.0, "A"],
        [466.2, "A#"],
        [493.9, "B"]
      ]
    },
    3: {
      name: "3. G-string",
      range: [191, 499],
      freqs: [
        [196.0, "G"],
        [207.7, "G#"],
        [220.0, "A"],
        [233.1, "A#"],
        [246.9, "B"],
        [261.6, "C"],
        [277.2, "C#"],
        [293.7, "D"],
        [311.1, "D#"],
        [329.6, "E"],
        [349.2, "F"],
        [370.0, "F#"],
        [392.0, "G"]
      ]
    },
    4: {
      name: "4. D-string",
      range: [142, 289],
      freqs: [
        [146.8, "D"],
        [155.6, "D#"],
        [164.8, "E"],
        [174.6, "F"],
        [185.0, "F#"],
        [196.0, "G"],
        [207.7, "G#"],
        [220.0, "A"],
        [233.1, "A#"],
        [246.9, "B"],
        [261.6, "C"],
        [277.2, "C#"],
        [293.7, "D"]
      ]
    },
    5: {
      name: "5. A-string",
      range: [105, 215],
      freqs: [
        [110.0, "A"],
        [116.5, "A#"],
        [123.5, "B"],
        [130.8, "C"],
        [138.6, "C#"],
        [146.8, "D"],
        [155.6, "D#"],
        [164.8, "E"],
        [174.6, "F"],
        [185.0, "F#"],
        [196.0, "G"],
        [207.7, "G#"],
        [220.0, "A"]
      ]
    },
    6: {
      name: "6. E-string (thickest)",
      range: [75, 170],
      freqs: [
        [82.4, "E"],
        [87.3, "F"],
        [92.5, "F#"],
        [98.0, "G"],
        [103.8, "G#"],
        [110.0, "A"],
        [116.5, "A#"],
        [123.5, "B"],
        [130.8, "C"],
        [138.6, "C#"],
        [146.8, "D"],
        [155.6, "D#"],
        [164.8, "E"],
      ]
    },
    7: {
      name: "7. For those who do the metal...",
      range: [55, 130],
      freqs: [
        [61.7, "B"],
        [65.4, "C"],
        [69.3, "C#"],
        [73.4, "D"],
        [77.8, "D#"],
        [82.4, "E"],
        [87.3, "F"],
        [92.5, "F#"],
        [98.0, "G"],
        [103.8, "G#"],
        [110.0, "A"],
        [116.5, "A#"],
        [123.5, "B"],
      ]
    }
  }
}


================================================
FILE: js/explosion_effect.js
================================================
// https://stackoverflow.com/questions/43498923/html5-canvas-particle-explosion
function ExplosionEffect(ctx) {
  const particlesPerExplosion = 25;
  const particlesMinSpeed     = 5;
  const particlesMaxSpeed     = 10;
  const particlesMinSize      = 2;
  const particlesMaxSize      = 4;
  var   explosions            = [];

  function particle(x, y, correctAnswer) {
    this.x    = x;
    this.y    = y;
    this.xv   = randInt(particlesMinSpeed, particlesMaxSpeed, false);
    this.yv   = randInt(particlesMinSpeed, particlesMaxSpeed, false);
    this.size = randInt(particlesMinSize, particlesMaxSize, true);
    this.color = correctAnswer ? gameConfig.colors.green : gameConfig.colors.red;
  }

  function explosion(x, y, correctAnswer) {
    this.particles = [];

    for (let i = 0; i < particlesPerExplosion; i++) {
      this.particles.push(
        new particle(x, y, correctAnswer)
      );
    }
  }

  return {
    draw: function() {
      if (explosions.length === 0) {
        return;
      }

      for (let i = 0; i < explosions.length; i++) {

        const explosion = explosions[i];
        const particles = explosion.particles;

        if (particles.length === 0) {
          explosions.splice(i, 1);
          return;
        }

        const particlesAfterRemoval = particles.slice();
        for (let ii = 0; ii < particles.length; ii++) {

          const particle = particles[ii];

          // Check particle size
          // If 0, remove
          if (particle.size <= 0) {
            particlesAfterRemoval.splice(ii, 1);
            continue;
          }

          ctx.beginPath();
          ctx.arc(particle.x, particle.y, particle.size, Math.PI * 2, 0, false);
          ctx.closePath();
          ctx.fillStyle = particle.color;
          ctx.fill();

          // Update
          particle.x += particle.xv;
          particle.y += particle.yv;
          particle.size -= .1;
        }

        explosion.particles = particlesAfterRemoval;
      }
    },
    add: function(x, y, correctAnswer) {
      explosions.push(
        new explosion(x, y, correctAnswer)
      );
    }
  }
}


================================================
FILE: js/fretboard.js
================================================
function Fretboard(canvas, songLoader, string, rockWidth, pegWidth) {
  var ctx = canvas.getContext("2d"),
      blockHeight = rockWidth,
      block = {
        x: 0,
        y: canvas.height - blockHeight,
        width: canvas.width,
        height: blockHeight
      };

  var highlightedFret,
      highlightedColor = gameConfig.colors.yellow;

  function drawCircle(x, y) {
    var circleSize = (blockHeight / 6 - 1) / 2;

    ctx.fillStyle = gameConfig.colors.white;
    ctx.beginPath();
    ctx.arc(x, y, circleSize, 0, 2 * Math.PI);
    ctx.fill();
  }

  function drawLine(x, y, x1, y1) {
    ctx.beginPath();
    ctx.moveTo(x, y);
    ctx.lineTo(x1, y1);
    ctx.stroke();
  }

  return {
    draw: function() {
      ctx.strokeStyle = gameConfig.colors.white;
      ctx.lineWidth = 1;
      for(var i = 1; i < gameConfig.strings[string].notes.length; i++) {
        var x = i * rockWidth + pegWidth;
        drawLine(x, block.y, x, canvas.height);
      }
      drawLine(0, block.y, canvas.width, block.y);

      // draw single circles
      var circleFrets = [2, 4, 6, 8];
      var cirlceColor = gameConfig.colors.white;
      var verticalMiddle = canvas.height - blockHeight / 2;
      var circleSize = (blockHeight / 6 - 1) / 2;

      circleFrets.forEach(function(fret) {
        drawCircle((rockWidth * fret - 1) + rockWidth / 2 + pegWidth, verticalMiddle);
      });

      // draw double circles
      var doubleCirclesFret = 12;
      drawCircle((rockWidth * 11) + rockWidth / 2 + pegWidth, canvas.height - circleSize * 2.5);
      drawCircle((rockWidth * 11) + rockWidth / 2 + pegWidth, canvas.height - block.height + 2.5 * circleSize);

      if(typeof(highlightedFret) === "number") {
        ctx.fillStyle = highlightedColor;
        ctx.fillRect(highlightedFret * rockWidth + pegWidth, block.y, rockWidth, rockWidth);
      }
    },
    highlightFret: function(note, color) {
      var fretIndex = songLoader.findNoteIndex(note, string);

      highlightedFret = fretIndex;
      highlightedColor = color ? color : gameConfig.colors.yellow;

      setTimeout(function() {
        highlightedFret = undefined;
      }, 100);
    }
  }
}


================================================
FILE: js/health_drawer.js
================================================
function HealthDrawer(ctx) {
  var heartWidth = 40;
  var heartHeight = 25;
  var c1 = 2;
  var c2 = 2;

  function drawBezierCurve(x0, y0, x1, y1, x2, y2, x3, y3) {
    ctx.moveTo(x0, y0);
    ctx.bezierCurveTo(x1, y1, x2, y2, x3, y3);
  }

  return {
    draw: function(health, isSandboxMode) {
      if(isSandboxMode) {
        return;
      }

      var prevFillStyle = ctx.fillStyle;
      var prevStrokeStyle = ctx.strojeStyle;

      ctx.fillStyle = gameConfig.colors.red;
      ctx.strokeStyle = gameConfig.colors.red;

      for(var i = 0; i < health; i++) {
        var x = ctx.canvas.width - heartWidth - i * (heartWidth + 10);
        var y = 20;

        ctx.beginPath();
        drawBezierCurve(x, y, x, y - heartHeight / 2, x - heartWidth / 2, y - heartHeight / 2, x - heartWidth / 2, y);
        drawBezierCurve(x - heartWidth / 2, y, x - heartWidth / 2, y + heartHeight / 2, x, y + heartHeight / 2 * c1, x, y + heartHeight / 2 * c2);
        drawBezierCurve(x, y + heartHeight / 2 * c2, x, y + heartHeight / 2 * c1, x + heartWidth / 2, y + heartHeight / 2, x + heartWidth / 2, y);
        drawBezierCurve(x + heartWidth / 2, y, x + heartWidth / 2, y - heartHeight / 2, x, y - heartHeight / 2, x, y);
        ctx.closePath();
        ctx.fill();

        ctx.beginPath();
        ctx.moveTo(x - heartWidth / 2, y);
        ctx.lineTo(x + heartWidth / 2, y);
        ctx.lineTo(x, y + heartHeight / 2 * c2);
        ctx.closePath();
        ctx.stroke();
        ctx.fill()
      }

      ctx.fillStyle = prevFillStyle;
      ctx.strokeStyle = prevStrokeStyle;
    }
  }
}


================================================
FILE: js/helper_functions.js
================================================
function randInt(min, max, positive) {
  let num;
  if (positive === false) {
    num = Math.floor(Math.random() * max) - min;
    num *= Math.floor(Math.random() * 2) === 1 ? 1 : -1;
  } else {
    num = Math.floor(Math.random() * max) + min;
  }

  return num;
}

function pickRandom(array) {
  return array[Math.floor(Math.random() * array.length)];
}

function getChromeVersion() {
  var raw = navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./);

  return raw ? parseInt(raw[2], 10) : false;
}

function randomArray(length, max) {
  return Array.apply(null, Array(length)).map(function() {
    return Math.round(Math.random() * max);
  });
}

$.fn.customerPopup = function (e, intWidth, intHeight, blnResize) {
  // Prevent default anchor event
  e.preventDefault();

  // Set values for window
  intWidth = intWidth || '500';
  intHeight = intHeight || '400';
  strResize = (blnResize ? 'yes' : 'no');

  // Set title and open popup with focus on it
  var strTitle = ((typeof this.attr('title') !== 'undefined') ? this.attr('title') : 'Social Share'),
      strParam = 'width=' + intWidth + ',height=' + intHeight + ',resizable=' + strResize,
      objWindow = window.open(this.attr('href'), strTitle, strParam).focus();
}


================================================
FILE: js/sharing.js
================================================
$(document).on("click", ".share-button", function(e) {
  var $this = $(this);

  ga("send", "event", "Game", "Share", $this.data("eventLabel"));

  $this.customerPopup(e);
});


================================================
FILE: js/song_loader.js
================================================
function SongLoader() {
  const randomSongLength = 10;

  Object.keys(gameConfig.strings).forEach(function(string) {
    var rows = gameConfig.strings[string].freqs.slice(1, 13);
    var notes = rows.map(function(row) {
      var note = row[1];

      return note;
    })

    gameConfig.strings[string].notes = notes;
  });

  const songs = {
    "Random notes": randomArray(20, 11).join("--------"),
    "Happy Birthday": "0-0-2--0--5-4----0-0-2--0----7-5----0-0-9--7-5-4--2-2----10-10-9--5--7-5",
    "Guess what": "0--3--5---0--3--6--5---0--3--5---3--0",

    "Abba: Money Money Money v2 (Tempo=200)": [['F', 4], ['G', 8], ['G#', 4], ['F', 8], ['G', 4], ['G#', 4], ['-', 4], ['G#', 4], ['F', 8], ['G', 4], ['G#', 4], ['-', 4], ['G', 4], ['F', 8], ['G#', 4], ['G#', 4], ['-', 16], ['F', 1]],

    "Coca Cola(Tempo=125)": [['A', 8], ['A', 8], ['A', 8], ['A', 8], ['A#', 4], ['A', 8], ['G', 4], ['G', 8], ['C', 8], ['A', 4], ['F', 4], ['-', 2]],

    "Eiffel 65: Blue v1 (Tempo=140)": [['A', 4], ['A#', 4], ['G', 8], ['A#', 8], ['C', 8], ['F', 8], ['A', 8], ['A#', 4], ['G', 8], ['A#', 8], ['D', 8], ['D#', 4], ['D', 8], ['C', 8], ['A#', 4], ['G', 8], ['A#', 8], ['C', 8], ['F', 8], ['A', 8], ['A#', 4], ['G', 8], ['A#', 8], ['D', 8], ['D#', 4], ['D', 8], ['C', 8], ['A#', 4], ['G', 8], ['A#', 8], ['C', 8], ['F', 8], ['A', 8], ['A#', 4], ['G', 8], ['A#', 8], ['D', 8], ['D#', 4], ['D', 8], ['C', 8], ['A#', 4], ['G', 8], ['A#', 8], ['A', 8], ['F', 8], ['F', 8], ['G', 2]],

    "Europe: The Final Countdown (Tempo=125)": [['-', 4], ['-', 8], ['C', 16], ['A#', 16], ['C', 4], ['F', 4], ['-', 4], ['-', 8], ['C#', 16], ['C', 16], ['C#', 8], ['C', 8], ['A#', 4], ['-', 4], ['-', 8], ['C#', 16], ['C', 16], ['C#', 4], ['F', 4], ['-', 4], ['-', 8], ['A#', 16], ['G#', 16], ['A#', 8], ['G#', 8], ['G', 8], ['A#', 8], ['G#', 4], ['-', 8], ['G', 16], ['G#', 16], ['A#', 4], ['-', 8], ['G#', 16], ['A#', 16], ['C', 8], ['A#', 8], ['G#', 8], ['G', 8], ['F', 4], ['C#', 4], ['C', 2], ['-', 4], ['C', 16], ['C#', 16], ['C', 16], ['A#', 16], ['C', 1]],

    "Haddaway: What is Love (Tempo=225)": [['A#', 4], ['A', 4], ['A#', 4], ['G', 4], ['A#', 4], ['A', 4], ['A#', 4], ['G', 4], ['A#', 4], ['A', 4], ['A#', 4], ['F', 4], ['A#', 4], ['A', 4], ['A#', 4], ['F', 4], ['A', 4], ['G', 4], ['A', 4], ['F', 4], ['A', 4], ['G', 4], ['A', 4], ['F', 4], ['A', 4], ['G', 4], ['A', 4], ['F', 4], ['A', 4], ['G', 4], ['A', 4], ['F', 4]],


    "James Bond: Tomorrow Never Dies (Tempo=125)": [['F', 8], ['G', 16], ['G', 16], ['G', 8], ['G', 4], ['F', 8], ['F', 8], ['F', 8], ['F', 8], ['G#', 16], ['G#', 16], ['G#', 8], ['G#', 4], ['G', 8], ['G', 8], ['G', 8], ['F', 8], ['G', 16], ['G', 16], ['G', 8], ['G', 4], ['F', 8], ['F', 8], ['F', 8], ['F', 8], ['G#', 16], ['G#', 16], ['G#', 8], ['G#', 4], ['G', 8], ['G', 8], ['G', 8]],


    "Nirvana: Come as You Are (Tempo=225)": [['F', 8], ['F', 8], ['F#', 8], ['G', 8], ['-', 4], ['-', 4], ['A#', 8], ['G', 8], ['A#', 8], ['G', 8], ['G', 8], ['F#', 8], ['F', 8], ['C', 8], ['F', 8], ['F', 8], ['-', 4], ['-', 4], ['C', 8], ['F', 8], ['F#', 8], ['G', 8], ['-', 4], ['-', 4], ['A#', 8], ['G', 8], ['A#', 8], ['G', 8], ['G', 8], ['F#', 8], ['F', 8], ['C', 8], ['F', 8], ['F', 8], ['-', 4], ['-', 4], ['C', 8]],


    "Ricky Martin: Livin La Vida Loca (Tempo=160)": [['A#', 16], ['-', 8], ['-', 16], ['A#', 4], ['-', 16], ['-', 32], ['F#', 8], ['G#', 8], ['B', 16], ['-', 8], ['-', 16], ['B', 16], ['-', 8], ['-', 16], ['A#', 4], ['-', 4], ['-', 8], ['A#', 16], ['-', 8], ['-', 16], ['A#', 4], ['-', 16], ['-', 32], ['F#', 8], ['F', 8], ['G#', 8], ['-', 8], ['G#', 8], ['-', 8], ['F#', 4], ['-', 4], ['-', 8], ['A#', 16], ['-', 8], ['-', 16], ['A#', 4], ['-', 8], ['F#', 8], ['G#', 8], ['B', 16], ['-', 8], ['-', 16], ['B', 16], ['-', 8], ['-', 16], ['A#', 4], ['-', 4], ['-', 8], ['A#', 16], ['-', 8], ['-', 16], ['A#', 4], ['-', 8], ['F#', 8], ['F', 8], ['G#', 16], ['-', 8], ['-', 16], ['G#', 8], ['-', 8], ['F#', 4], ['-', 8]],

    "Smoke on the Water (Tempo=112)": [['F', 4], ['G#', 4], ['A#', 4], ['F', 4], ['G#', 4], ['B', 8], ['A#', 4], ['-', 4], ['F', 4], ['G#', 4], ['A#', 4], ['G#', 4], ['F', 4], ['-', 2], ['-', 8], ['F', 4], ['G#', 4], ['A#', 4], ['F', 4], ['G#', 4], ['B', 8], ['A#', 4], ['-', 4], ['F', 4], ['G#', 4], ['A#', 4], ['G#', 4], ['F', 4], ['-', 4]]
  }

  function parseSong(encodedSong, string) {
    let song = [];
    let duration = 0;
    let last_note;
    for (let i = 0; i < encodedSong.length; i++){
      if (encodedSong[i] != "-"){
        if (duration > 0){
          song.push([last_note, 8 / duration]);
        }

        let fret = parseInt(encodedSong[i]);
        last_note = fret === 0 ? "E" : gameConfig.strings[string].notes[fret - 1];
        duration = 0;
      } else {
        duration += 1;
      }
    }
    song.push([last_note, 8/8.0]);
    return song;
  }


  return {
    loadSong: function(songIndex, string) {
      var encodedSong = songs[songIndex];
      if (encodedSong.constructor === Array){
        return encodedSong;
      }
      return parseSong(encodedSong, string);
    },
    findNoteIndex: function(note, string) {
      return gameConfig.strings[string].notes.findIndex(function(n) {
        return note === n;
      });
    },
    populateSelectMenu: function($songSelect) {
      for(song in songs) {
        var $option = $("<option/>");
        $option.val(song);
        $option.text(song);

        if(song === "Random") {
          $option.attr("selected", "selected");
        }

        $songSelect.append($option);
      }
    }
  }
}
Download .txt
gitextract_95i3534h/

├── LICENSE
├── README.md
├── css/
│   └── app.css
├── index.html
└── js/
    ├── app.js
    ├── audio_processor.js
    ├── audio_wave.js
    ├── config.js
    ├── explosion_effect.js
    ├── fretboard.js
    ├── health_drawer.js
    ├── helper_functions.js
    ├── sharing.js
    └── song_loader.js
Download .txt
SYMBOL INDEX (21 symbols across 8 files)

FILE: js/app.js
  function initRocks (line 71) | function initRocks(songIndex, string) {
  function calculateRockY (line 81) | function calculateRockY(rockIndex) {
  function addRock (line 88) | function addRock(rockIndex, song) {
  function toggleMenuCopy (line 107) | function toggleMenuCopy(type) {
  function stopGame (line 115) | function stopGame() {
  function animate (line 135) | function animate() {
  function isColliding (line 180) | function isColliding(rock) {
  function decrementScore (line 184) | function decrementScore() {
  function clearBackground (line 189) | function clearBackground() {
  function drawAll (line 198) | function drawAll() {
  function showNoMic (line 260) | function showNoMic() {

FILE: js/audio_processor.js
  function AudioProcessor (line 2) | function AudioProcessor() {

FILE: js/audio_wave.js
  function AudioWaveChart (line 1) | function AudioWaveChart() {

FILE: js/explosion_effect.js
  function ExplosionEffect (line 2) | function ExplosionEffect(ctx) {

FILE: js/fretboard.js
  function Fretboard (line 1) | function Fretboard(canvas, songLoader, string, rockWidth, pegWidth) {

FILE: js/health_drawer.js
  function HealthDrawer (line 1) | function HealthDrawer(ctx) {

FILE: js/helper_functions.js
  function randInt (line 1) | function randInt(min, max, positive) {
  function pickRandom (line 13) | function pickRandom(array) {
  function getChromeVersion (line 17) | function getChromeVersion() {
  function randomArray (line 23) | function randomArray(length, max) {

FILE: js/song_loader.js
  function SongLoader (line 1) | function SongLoader() {
Condensed preview — 14 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (51K chars).
[
  {
    "path": "LICENSE",
    "chars": 1075,
    "preview": "MIT License\n\nCopyright (c) 2017 Anatoli Makarevich\n\nPermission is hereby granted, free of charge, to any person obtainin"
  },
  {
    "path": "README.md",
    "chars": 613,
    "preview": "# Guitar Bro – browser game that helps you learn notes on guitar\n\n![Guitar Bro Snapshot](https://user-images.githubuserc"
  },
  {
    "path": "css/app.css",
    "chars": 5781,
    "preview": "html, body {\n  width:  100%;\n  height: 100%;\n  margin: 0;\n  padding: 0;\n  overflow: hidden;\n}\n\nhtml {\n  box-sizing: bord"
  },
  {
    "path": "index.html",
    "chars": 8668,
    "preview": "<!DOCTYPE html>\n<html>\n<head>\n  <title>Guitar Bro – browser game with a real guitar.</title>\n\n  <meta property=\"og:url\" "
  },
  {
    "path": "js/app.js",
    "chars": 9331,
    "preview": "$(function() {\n  var $game = $(\".real-guitar-hero\"),\n      $score = $game.find(\".score__points\");\n\n  var $settings = $(\""
  },
  {
    "path": "js/audio_processor.js",
    "chars": 4897,
    "preview": "// https://github.com/GoogleChrome/guitar-tuner\nfunction AudioProcessor() {\n  this.FFTSIZE = 2048 * 4;\n  this.stream = n"
  },
  {
    "path": "js/audio_wave.js",
    "chars": 1784,
    "preview": "function AudioWaveChart() {\n    var $audioWave = $(\".audio-wave\");\n\n    var w = $audioWave.width();\n    var h = $audioWa"
  },
  {
    "path": "js/config.js",
    "chars": 2875,
    "preview": "var gameConfig = {\n  fps: 50,\n  colors: {\n    green: \"#9BC53D\",\n    yellow: \"#FDE74C\",\n    red: \"#E55934\",\n    white: \"#"
  },
  {
    "path": "js/explosion_effect.js",
    "chars": 2122,
    "preview": "// https://stackoverflow.com/questions/43498923/html5-canvas-particle-explosion\nfunction ExplosionEffect(ctx) {\n  const "
  },
  {
    "path": "js/fretboard.js",
    "chars": 2163,
    "preview": "function Fretboard(canvas, songLoader, string, rockWidth, pegWidth) {\n  var ctx = canvas.getContext(\"2d\"),\n      blockHe"
  },
  {
    "path": "js/health_drawer.js",
    "chars": 1588,
    "preview": "function HealthDrawer(ctx) {\n  var heartWidth = 40;\n  var heartHeight = 25;\n  var c1 = 2;\n  var c2 = 2;\n\n  function draw"
  },
  {
    "path": "js/helper_functions.js",
    "chars": 1231,
    "preview": "function randInt(min, max, positive) {\n  let num;\n  if (positive === false) {\n    num = Math.floor(Math.random() * max) "
  },
  {
    "path": "js/sharing.js",
    "chars": 176,
    "preview": "$(document).on(\"click\", \".share-button\", function(e) {\n  var $this = $(this);\n\n  ga(\"send\", \"event\", \"Game\", \"Share\", $t"
  },
  {
    "path": "js/song_loader.js",
    "chars": 5541,
    "preview": "function SongLoader() {\n  const randomSongLength = 10;\n\n  Object.keys(gameConfig.strings).forEach(function(string) {\n   "
  }
]

About this extraction

This page contains the full source code of the makaroni4/guitar_bro GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 14 files (46.7 KB), approximately 14.7k tokens, and a symbol index with 21 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!