<!DOCTYPE html>
<html lang="en" >

<head>

  <meta charset="UTF-8">
  
<link rel="apple-touch-icon" type="image/png" href="https://cpwebassets.codepen.io/assets/favicon/apple-touch-icon-5ae1a0698dcc2402e9712f7d01ed509a57814f994c660df9f7a952f3060705ee.png" />
<meta name="apple-mobile-web-app-title" content="CodePen">

<link rel="shortcut icon" type="image/x-icon" href="https://cpwebassets.codepen.io/assets/favicon/favicon-aec34940fbc1a6e787974dcd360f2c6b63348d4b1f4e06c77743096d55480f33.ico" />

<link rel="mask-icon" type="image/x-icon" href="https://cpwebassets.codepen.io/assets/favicon/logo-pin-8f3771b1072e3c38bd662872f6b673a722f4b3ca2421637d5596661b4e2132cc.svg" color="#111" />


  <title>CodePen - MidiMessaging</title>
  
  
  
  
<style>
html,
body {
  margin: 0;
  padding: 0;
}

#main,
.dash {
  display: flex;
  width: 80%;
  align-items: center;
  justify-content: center;
  margin: 10vmin auto;
  flex-direction: column;
  flex-grow: 5;
}

.dash {
  /*display:none;*/
}

span,
span > em {
  color: red;
  background: #06C7BD;
  position: relative;
  text-align: center;
}

strong,
i {
  font-weight: bold;
  font-family: "Droid Sans Mono", sans-serif;
}

em {
  font-style: normal;
  width: 60px;
  height: 60px;
  align-items: center;
  justify-content: center;
  float: left;
  display: inline-flex;
  background: #efefef;
  border-radius: 60px;
  text-align: center;
  margin: 2px;
  flex-direction: column;
  overflow: hidden;
}

table {
  width: 100%;
}

td {
  text-align: center;
  width: 33%;
}

button,
p {
  width: 100%;
}

small {
  font-size: 0.5rem;
}

.slider {
  -webkit-appearance: none;
     -moz-appearance: none;
          appearance: none;
  width: 400px;
  height: 5px;
  background: #d3d3d3;
  outline: none;
  opacity: 0.7;
}
</style>

  <script>
  window.console = window.console || function(t) {};
</script>

  
  
  <script>
  if (document.location.search.match(/type=embed/gi)) {
    window.parent.postMessage("resize", "*");
  }
</script>


</head>

<body translate="no">
  <div id="main">
  <h5 id="title">Lower Volume, click text, click text again to pause</h5>
  <div class="sentence">
    <p class='playMe'></p>
    <button>Change Text</button>
  </div>
</div>
<div class="dash">
  <strong></strong>
  <label for="range">Tempo
        <input id="range" type="range" class="slider" min="2" max="170" step="1" value="6"  oninput="range_disp.value = range.value"//> <output  id="range_disp"></output> </label>
  <table>
    <thead>
      <tr>
        <th>Note</th>
        <th>Frequency</th>
        <th>Location</th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <td>None</td>
        <td>0</td>
        <td>0</td>
      </tr>
    </tbody>
  </table>
</div>
    <script src="https://cpwebassets.codepen.io/assets/common/stopExecutionOnTimeout-1b93190375e9ccc259df3a57c1abc0e64599724ae30d7ea4c6877eb615f89387.js"></script>

  
      <script id="rendered-js" >
class midiMusic {
  constructor(base, text) {
    this.el = document.querySelector('.playMe');
    this.audioContext = window.AudioContext || window.webkitAudioContext;
    this.audio = true;
    this.osc = true;
    this.dash = document.querySelector('.dash');
    this.td = this.dash.querySelectorAll('tr')[1].children;
    this.notePanel = this.dash.querySelector('strong');
    this.position = -2;
    this.playing = false;
    this.base = base || 30;
    this.scaleSet = [];
    this.lettersl = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'];
    this.lettersu = this.lettersl.map(e => {return e.toUpperCase();});
    this.numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
    this.symbols = [' ', '-', '?', ',', '/', '.', '!'];
    this.allkeys = this.symbols.concat(this.lettersl, this.numbers, this.lettersu).join('');
    this.scale = {
      'C0': [16.35, 2109.89],
      'C#0/Db0': [17.32, 1991.47],
      'D0': [18.35, 1879.69],
      'D#0/Eb0': [19.45, 1774.2],
      'E0': [20.6, 1674.62],
      'F0': [21.83, 1580.63],
      'F#0/Gb0': [23.12, 1491.91],
      'G0': [24.5, 1408.18],
      'G#0/Ab0': [25.96, 1329.14],
      'A0': [27.5, 1254.55],
      'A#0/Bb0': [29.14, 1184.13],
      'B0': [30.87, 1117.67],
      'C1': [32.7, 1054.94],
      'C#1/Db1': [34.65, 995.73],
      'D1': [36.71, 939.85],
      'D#1/Eb1': [38.89, 887.1],
      'E1': [41.2, 837.31],
      'F1': [43.65, 790.31],
      'F#1/Gb1': [46.25, 745.96],
      'G1': [49, 704.09],
      'G#1/Ab1': [51.91, 664.57],
      'A1': [55, 627.27],
      'A#1/Bb1': [58.27, 592.07],
      'B1': [61.74, 558.84],
      'C2': [65.41, 527.47],
      'C#2/Db2': [69.3, 497.87],
      'D2': [73.42, 469.92],
      'D#2/Eb2': [77.78, 443.55],
      'E2': [82.41, 418.65],
      'F2': [87.31, 395.16],
      'F#2/Gb2': [92.5, 372.98],
      'G2': [98, 352.04],
      'G#2/Ab2': [103.83, 332.29],
      'A2': [110, 313.64],
      'A#2/Bb2': [116.54, 296.03],
      'B2': [123.47, 279.42],
      'C3': [130.81, 263.74],
      'C#3/Db3': [138.59, 248.93],
      'D3': [146.83, 234.96],
      'D#3/Eb3': [155.56, 221.77],
      'E3': [164.81, 209.33],
      'F3': [174.61, 197.58],
      'F#3/Gb3': [185, 186.49],
      'G3': [196, 176.02],
      'G#3/Ab3': [207.65, 166.14],
      'A3': [220, 156.82],
      'A#3/Bb3': [233.08, 148.02],
      'B3': [246.94, 139.71],
      'C4': [261.63, 131.87],
      'C#4/Db4': [277.18, 124.47],
      'D4': [293.66, 117.48],
      'D#4/Eb4': [311.13, 110.89],
      'E4': [329.63, 104.66],
      'F4': [349.23, 98.79],
      'F#4/Gb4': [369.99, 93.24],
      'G4': [392, 88.01],
      'G#4/Ab4': [415.3, 83.07],
      'A4': [440, 78.41],
      'A#4/Bb4': [466.16, 74.01],
      'B4': [493.88, 69.85],
      'C5': [523.25, 65.93],
      'C#5/Db5': [554.37, 62.23],
      'D5': [587.33, 58.74],
      'D#5/Eb5': [622.25, 55.44],
      'E5': [659.25, 52.33],
      'F5': [698.46, 49.39],
      'F#5/Gb5': [739.99, 46.62],
      'G5': [783.99, 44.01],
      'G#5/Ab5': [830.61, 41.54],
      'A5': [880, 39.2],
      'A#5/Bb5': [932.33, 37],
      'B5': [987.77, 34.93],
      'C6': [1046.5, 32.97],
      'C#6/Db6': [1108.73, 31.12],
      'D6': [1174.66, 29.37],
      'D#6/Eb6': [1244.51, 27.72],
      'E6': [1318.51, 26.17],
      'F6': [1396.91, 24.7],
      'F#6/Gb6': [1479.98, 23.31],
      'G6': [1567.98, 22],
      'G#6/Ab6': [1661.22, 20.77],
      'A6': [1760, 19.6],
      'A#6/Bb6': [1864.66, 18.5],
      'B6': [1975.53, 17.46],
      'C7': [2093, 16.48],
      'C#7/Db7': [2217.46, 15.56],
      'D7': [2349.32, 14.69],
      'D#7/Eb7': [2489.02, 13.86],
      'E7': [2637.02, 13.08],
      'F7': [2793.83, 12.35],
      'F#7/Gb7': [2959.96, 11.66],
      'G7': [3135.96, 11],
      'G#7/Ab7': [3322.44, 10.38],
      'A7': [3520, 9.8],
      'A#7/Bb7': [3729.31, 9.25],
      'B7': [3951.07, 8.73],
      'C8': [4186.01, 8.24],
      'C#8/Db8': [4434.92, 7.78],
      'D8': [4698.63, 7.34],
      'D#8/Eb8': [4978.03, 6.93],
      'E8': [5274.04, 6.54],
      'F8': [5587.65, 6.17],
      'F#8/Gb8': [5919.91, 5.83],
      'G8': [6271.93, 5.5],
      'G#8/Ab8': [6644.88, 5.19],
      'A8': [7040, 4.9],
      'A#8/Bb8': [7458.62, 4.63],
      'B8': [7902.13, 4.37] };

    this.el.onclick = e => {
      this.audio.resume();
      this.playing = !this.playing;
      this.play().then();
    };
    document.querySelector('button').onclick = e => {
      this.position = -1;
      this.playing = true;
      this.text = prompt('Type a sentence or parse song to listen to') || 'Oops! You forgot to pick some text.';
      this.play().then();
    };
    this.el.innerText = text;
    this.text = this.allkeys;
    this.scaleSet = this.setupScale(this.base, this.base + this.text.length);
    this.notePanel.innerHTML = this.allkeys.split('').map((a, b, c) => {let cn = a === ' ' ? '<sub>[space]</sub>' : a;
      return c[b] === this.currentNote() ? '<em>' + cn + '<small>' + this.scaleSet[this.allkeys.indexOf(a)] + '</small></em>' : '<em>' + cn + '<small>' + this.scaleSet[this.allkeys.indexOf(a)] + '</small></em>';
    }).join('');
    this.buildOSC();
    this.text = text || this.text;
    this.cells = this.notePanel.querySelectorAll('em');
  }
  setupScale(start, stop) {
    let scaleSet = [];
    Object.keys(this.scale).map((a, b, c) => {if (b >= start && b <= stop) scaleSet.push(a);});
    return scaleSet;
  }
  buildOSC() {
    this.audio = new this.audioContext();
    this.osc = this.audio.createOscillator();
    this.osc.type = 'triangle';
    this.osc.connect(this.audio.destination);
    this.osc.start(1);
    this.osc.disconnect();
  }
  spanit(e) {return '<span>' + e + '</span>';}
  updateText(note, freq) {
    Array(note, freq, this.position).forEach((a, b) => {this.td[b].innerHTML = this.position >= 0 ? a : '';});
    this.el.innerHTML = this.text.substring(0, this.position) + this.spanit(this.currentNote()) + this.text.substring(this.position + 1, this.text.length);
    let span = this.notePanel.querySelector('span');
    if (span) span.parentNode.innerHTML = span.innerHTML;
    this.cells[this.scaleSet.indexOf(note)].innerHTML = this.spanit('<em>' + this.currentNote() + '<small>' + note + '</small></em>');
    this.cells[0].remove();
  }
  currentFreq() {let freq = this.scale[this.scaleSet[this.allkeys.indexOf(this.currentNote())]] || false;return freq[0] || false;}
  currentNote() {return this.text[this.position] || '';}
  resetPosition() {this.position = -1;this.playing = false;this.osc.disconnect();}
  async play() {
    this.position++;
    if (this.position >= this.text.length) this.resetPosition();
    this.updateText(this.scaleSet[this.allkeys.indexOf(this.currentNote())], this.currentFreq(), this.position);
    if (this.playing && this.position >= 0) {
      if (this.currentNote() !== ' ') {
        this.osc.frequency.setValueAtTime(this.currentFreq(), this.audio.currentTime);
        this.osc.connect(this.audio.destination);
      }
      setTimeout(() => {if (this.playing) {this.play().then();}}, 1000 / document.querySelector('#range').value);
    } else
    {this.osc.disconnect();}
  }}

let midi = new midiMusic(24, location.hash ? location.hash.slice(1) : 'This is default text');
midi.play().then();

function repeat()