├── readme.md ├── css └── styles.css ├── js └── binary-music-player.js └── index.html /readme.md: -------------------------------------------------------------------------------- 1 | ### Binary Music Player 2 | 3 | As the binary counter counts (upwards) each column is given a musical note. If that column were to have a `1` in it, the note will play as it changes. 4 | 5 | The binary music player is based around this video which is using real piano sounds & sounds a lot more elegant - and is built with Tone.JS and some relatively vanilla js/css/html. 6 | 7 | The creations are shareable /w url parameters, for example `?keys=G1,C^2,E2,G^2,D3,E3,G3,B4,D4,F^4`. These will update when you customize your own creation. 8 | 9 |  10 | -------------------------------------------------------------------------------- /css/styles.css: -------------------------------------------------------------------------------- 1 | @import "https://fonts.googleapis.com/css?family=Source+Code+Pro"; 2 | 3 | * { 4 | box-sizing: border-box; 5 | } 6 | 7 | html, 8 | body { 9 | margin: 0px; 10 | min-height: 100%; 11 | height: 100%; 12 | font-family: "Source Code Pro", monospace; 13 | } 14 | 15 | .containers { 16 | height: 100%; 17 | display: flex; 18 | flex-direction: column; 19 | } 20 | 21 | .binary-container { 22 | position: relative; 23 | flex-grow: 1; 24 | display: flex; 25 | flex-direction: column; 26 | justify-content: center; 27 | align-items: center; 28 | } 29 | 30 | .binary { 31 | font-size: 50px; 32 | } 33 | 34 | .play-pause { 35 | font-family: serif; 36 | font-size: 30px; 37 | margin-top: 20px; 38 | cursor: pointer; 39 | } 40 | 41 | .editor-container { 42 | transition: height 150ms ease-out; 43 | width: 100%; 44 | background: #e9e9e9; 45 | bottom: 0px; 46 | left: 0px; 47 | height: 42px; 48 | overflow: hidden; 49 | padding-top: 11px; 50 | } 51 | 52 | .editor-container-contents { 53 | transition: opacity 250ms ease-out; 54 | opacity: 0; 55 | } 56 | 57 | .editor-container.open { 58 | height: 400px; 59 | } 60 | 61 | .editor-container.open .editor-container-contents { 62 | opacity: 1; 63 | } 64 | 65 | .play { 66 | width: 39px; 67 | font-size: 15px; 68 | height: 35px; 69 | border: 3px solid; 70 | margin-top: 10px; 71 | border-radius: 3px; 72 | position: relative; 73 | } 74 | 75 | .play:after { 76 | content: ""; 77 | display: block; 78 | position: absolute; 79 | top: 50%; 80 | left: 50%; 81 | border-right: 8px solid transparent; 82 | border-top: 8px solid transparent; 83 | border-bottom: 8px solid transparent; 84 | border-left: 10px solid black; 85 | transform: translate(calc(-50% + 5px), -50%); 86 | } 87 | 88 | .pause:after { 89 | border: 6px solid black; 90 | transform: translate(-50%, -50%); 91 | } 92 | 93 | /** 94 | * The Keyboard 95 | */ 96 | 97 | ul { 98 | position: relative; 99 | width: 625px; 100 | margin: auto; 101 | padding: 0px; 102 | height: 90px; 103 | } 104 | 105 | li { 106 | position: relative; 107 | list-style: none; 108 | float: left; 109 | padding: 0; 110 | margin: 0; 111 | } 112 | 113 | .piano-key { 114 | transition: background 100ms ease; 115 | } 116 | 117 | ul .white { 118 | border-bottom: 1px solid #ccc; 119 | border-left: 1px solid #eee; 120 | background: #fff; 121 | height: 80px; 122 | width: 12px; 123 | z-index: 1; 124 | } 125 | 126 | .white:hover { 127 | background: #eee; 128 | } 129 | 130 | .black { 131 | margin: 0 -4px 0 -4px; 132 | border: 1px solid #000; 133 | background: black; 134 | height: 50px; 135 | width: 8px; 136 | z-index: 2; 137 | } 138 | 139 | .black:hover { 140 | background: #444; 141 | } 142 | 143 | .notes { 144 | margin: auto; 145 | display: block; 146 | position: relative; 147 | text-align: center; 148 | font-size: 24px; 149 | margin-top: 50px; 150 | margin-bottom: 40px; 151 | } 152 | 153 | .notes input { 154 | border: none; 155 | width: 55px; 156 | font-size: inherit; 157 | border-bottom: 4px solid #222; 158 | outline: 0px; 159 | text-align: center; 160 | padding: 5px; 161 | } 162 | 163 | .notes input:focus { 164 | border-bottom: 4px solid hotpink; 165 | } 166 | 167 | .info h1 { 168 | text-align: center; 169 | color: #222; 170 | letter-spacing: 2px; 171 | font-size: 14px; 172 | cursor: pointer; 173 | margin-top: 0px; 174 | } 175 | 176 | .info span { 177 | margin-left: 10px; 178 | margin-right: 15px; 179 | letter-spacing: -2px; 180 | } 181 | 182 | .details { 183 | max-width: 700px; 184 | padding: 30px; 185 | margin: auto; 186 | text-align: center; 187 | font-size: 15px; 188 | } 189 | 190 | @media only screen and (max-width: 700px) { 191 | .editor-container.open { 192 | height: auto; 193 | } 194 | 195 | .notes input { 196 | margin-bottom: 8px; 197 | } 198 | 199 | .info h1 { 200 | font-size: 12px; 201 | font-weight: bold; 202 | letter-spacing: 0px; 203 | } 204 | 205 | .notes { 206 | margin-top: 20px; 207 | margin-bottom: 20px; 208 | } 209 | 210 | .details { 211 | font-size: 10px; 212 | padding: 10px; 213 | } 214 | } 215 | -------------------------------------------------------------------------------- /js/binary-music-player.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Inspired by: 1023.exe (by Alfred Kedhammar) - /w Tone.js 3 | * -- Original video concept here: https://www.youtube.com/watch?v=CdaPhpGG6As 4 | * ---- Not quite as cool with the backing violins, but still cool! 5 | */ 6 | 7 | (function() { 8 | var count = 0; 9 | var played = false; 10 | var playing = false; 11 | var pad = "0000000000"; 12 | var numberArea = document.querySelector(".binary"); 13 | var previousBinarySet = pad.split(""); 14 | var currentFocusedInput = 0; 15 | 16 | var notes = ["D4", "E4", "F4", "G4", "A5", "C5", "D5", "E5", "F5", "G5"]; 17 | 18 | var times = [1.7, 1.5, 1.3, 1.2, 0.9, 0.7, 0.6, 0.5, 0.5, 0.4]; 19 | 20 | var customizeButton, customizeArea, inputAreas, keys, play; 21 | 22 | function toBinary(number) { 23 | return (number >>> 0).toString(2); 24 | } 25 | 26 | function padNumber(numberString) { 27 | return pad.substring(0, pad.length - numberString.length) + numberString; 28 | } 29 | 30 | function playOrPause() { 31 | if (playing) { 32 | Tone.Transport.stop(); 33 | count = 0; 34 | playing = false; 35 | pad = "0000000000"; 36 | previousBinarySet = pad.split(""); 37 | play.classList.toggle('pause'); 38 | return; 39 | } 40 | 41 | if (!played) { 42 | var synth = new Tone.PolySynth(8, Tone.Synth, { 43 | oscillator: { 44 | partials: [0, 2, 3, 4] 45 | } 46 | }).toMaster(); 47 | 48 | Tone.Transport.bpm.value = 50; 49 | 50 | Tone.Transport.scheduleRepeat(function(time) { 51 | var number = Math.floor(count); 52 | count = count + 1; 53 | var numberString = padNumber(toBinary(number)); 54 | numberArea.innerHTML = numberString; 55 | 56 | var binaryArray = numberString.split(""); 57 | 58 | for (var i = 0; i < binaryArray.length; i++) { 59 | if ( 60 | binaryArray[i] === "1" && 61 | previousBinarySet[i] !== binaryArray[i] 62 | ) { 63 | synth.triggerAttackRelease(notes[i], times[i], time); 64 | } 65 | } 66 | 67 | previousBinarySet = binaryArray; 68 | 69 | if (count > 1023) { 70 | count = 0; 71 | } 72 | }, "32i"); 73 | 74 | played = true; 75 | } 76 | 77 | playing = true; 78 | play.classList.toggle('pause'); 79 | Tone.Transport.start(); 80 | } 81 | 82 | function cacheDOM() { 83 | customizeButton = document.querySelector(".info"); 84 | customizeArea = document.querySelector(".editor-container"); 85 | inputAreas = document.querySelectorAll(".notes input"); 86 | keys = document.querySelectorAll(".piano-key"); 87 | play = document.querySelector(".play"); 88 | } 89 | 90 | function bindUI() { 91 | readNotesFromUrl(); 92 | 93 | customizeButton.addEventListener("click", onCustomizeButtonClicked, false); 94 | for (var i = 0; i < keys.length; i++) { 95 | keys[i].addEventListener("click", e => onKeyClicked(e), false); 96 | } 97 | 98 | for (var i = 0; i < inputAreas.length; i++) { 99 | inputAreas[i].value = notes[i]; 100 | inputAreas[i].addEventListener("click", e => { 101 | currentFocusedInput = parseInt(e.target.dataset.index); 102 | }); 103 | } 104 | 105 | play.addEventListener("click", playOrPause, false); 106 | } 107 | 108 | function onCustomizeButtonClicked() { 109 | if (!customizeArea.classList.contains("open")) { 110 | inputAreas[0].focus(); 111 | currentFocusedInput = 0; 112 | } 113 | customizeArea.classList.toggle("open"); 114 | } 115 | 116 | function setNotesInUrl() { 117 | history.replaceState && 118 | history.replaceState( 119 | {}, 120 | "", 121 | `?keys=${notes.join(",")}`.replace(/\#/g, "^") 122 | ); 123 | } 124 | 125 | function readNotesFromUrl() { 126 | const url = document.location.href; 127 | const urlData = new URL(url); 128 | const keys = urlData.searchParams.get("keys"); 129 | 130 | if (!keys) { 131 | return; 132 | } 133 | 134 | const keyData = keys.replace(/\^/g, "#").split(","); 135 | 136 | if (keyData.length === 10) { 137 | for (var i = 0; i < keyData.length; i++) { 138 | const el = document.querySelector(`[data-piano-key="${keyData[i]}"]`); 139 | if (!el) { 140 | return; 141 | } 142 | } 143 | 144 | notes = keyData; 145 | } 146 | } 147 | 148 | function onKeyClicked(e) { 149 | const valueSelected = e.target.dataset.pianoKey; 150 | inputAreas[currentFocusedInput].value = valueSelected; 151 | notes[currentFocusedInput] = valueSelected; 152 | 153 | currentFocusedInput = currentFocusedInput + 1; 154 | if (currentFocusedInput >= inputAreas.length) { 155 | currentFocusedInput = 0; 156 | } 157 | inputAreas[currentFocusedInput].focus(); 158 | 159 | setNotesInUrl(); 160 | } 161 | 162 | cacheDOM(); 163 | bindUI(); 164 | })(); 165 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 |