-
-
Save benwilhelm/77c47e8b0d828969cedbffaad3a0357b to your computer and use it in GitHub Desktop.
Banjo with UI and Sound
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class Banjo extends EventTarget { | |
muted = false | |
strings = [ | |
new BanjoString({ basePitch: 62 }), | |
new BanjoString({ basePitch: 59 }), | |
new BanjoString({ basePitch: 55 }), | |
new BanjoString({ basePitch: 50 }), | |
new BanjoString({ basePitch: 67 }) | |
] | |
fretString(stringIndex, fret) { | |
if (stringIndex === 4 && fret > 0 && fret < 6) { | |
throw new Error (`Fret ${fret} does not exist on stringIndex 4`) | |
} | |
const fretPosition = (stringIndex === 4 && fret !== 0) | |
? fret - 5 | |
: fret | |
this.strings[stringIndex].fret(fretPosition) | |
} | |
pickString(stringIndex, strength) { | |
try { | |
const stringTone = this.strings[stringIndex].pick(strength) | |
const emittedTone = { | |
...stringTone, | |
stringIndex, | |
volume: this.muted ? stringTone.volume / 2 : stringTone.volume | |
} | |
this.dispatchEvent(new CustomEvent('tone', { detail: emittedTone })) | |
return emittedTone | |
} catch (err) { | |
console.warn(err.message) | |
return null | |
} | |
} | |
} | |
class BanjoString { | |
fretPosition = 0 | |
broken = false | |
constructor(opts = { basePitch: 0 }) { | |
this.basePitch = opts.basePitch | |
} | |
fret(fretPosition) { | |
this.fretPosition = fretPosition | |
} | |
pick(strength) { | |
// if string is already broken, throw early | |
if (this.broken) { | |
throw new Error("Can't pick a broken string") | |
} | |
// correlate chance of breakage to strength of pick | |
const breakChance = strength * .00001 | |
if (breakChance > Math.random()) { | |
this.broken = true | |
throw new Error("String Broke") | |
} | |
return { | |
pitch: this.basePitch + this.fretPosition, | |
volume: strength, | |
duration: strength * strength | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<meta http-equiv="X-UA-Compatible" content="ie=edge"> | |
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" integrity="sha384-JcKb8q3iqJ61gNV9KGb8thSsNjpSL0n8PARn9HuZOnIxN0hoP+VmmDGMN5t9UJ0Z" crossorigin="anonymous"> | |
<link rel="stylesheet" href="./style.css"> | |
<title>Banjo</title> | |
</head> | |
<body> | |
<div class="container"> | |
<p> | |
<button class="btn btn-primary" id="initialize">Initialize Synth</button> | |
</p> | |
<div class="string string-4"> | |
<input type="radio" name="fret-4" checked value="0" class="fret open" data-string="4"> | |
<input type="radio" name="fret-4" value="1" class="fret" data-string="4"> | |
<input type="radio" name="fret-4" value="2" class="fret" data-string="4"> | |
<input type="radio" name="fret-4" value="3" class="fret" data-string="4"> | |
<input type="radio" name="fret-4" value="4" class="fret" data-string="4"> | |
<input type="radio" name="fret-4" value="5" class="fret" data-string="4"> | |
<input type="radio" name="fret-4" value="6" class="fret" data-string="4"> | |
<input type="radio" name="fret-4" value="7" class="fret" data-string="4"> | |
<input type="radio" name="fret-4" value="8" class="fret" data-string="4"> | |
<input type="radio" name="fret-4" value="9" class="fret" data-string="4"> | |
<input type="radio" name="fret-4" value="10" class="fret" data-string="4"> | |
<input type="range" class="strength" min="0" max="100" step="1" data-string="4" name="strength-4" /> | |
<button type="button" class="btn btn-primary pick" data-string="4" name="pick-4">Pick</button> | |
</div> | |
<div class="string string-3"> | |
<input type="radio" name="fret-3" checked value="0" class="fret open" data-string="3"> | |
<input type="radio" name="fret-3" value="1" class="fret" data-string="3"> | |
<input type="radio" name="fret-3" value="2" class="fret" data-string="3"> | |
<input type="radio" name="fret-3" value="3" class="fret" data-string="3"> | |
<input type="radio" name="fret-3" value="4" class="fret" data-string="3"> | |
<input type="radio" name="fret-3" value="5" class="fret" data-string="3"> | |
<input type="radio" name="fret-3" value="6" class="fret" data-string="3"> | |
<input type="radio" name="fret-3" value="7" class="fret" data-string="3"> | |
<input type="radio" name="fret-3" value="8" class="fret" data-string="3"> | |
<input type="radio" name="fret-3" value="9" class="fret" data-string="3"> | |
<input type="radio" name="fret-3" value="10" class="fret" data-string="3"> | |
<input type="range" class="strength" min="0" max="100" step="1" data-string="3" name="strength-3" /> | |
<button type="button" class="btn btn-primary pick" data-string="3" name="pick-3">Pick</button> | |
</div> | |
<div class="string string-2"> | |
<input type="radio" name="fret-2" checked value="0" class="fret open" data-string="2"> | |
<input type="radio" name="fret-2" value="1" class="fret" data-string="2"> | |
<input type="radio" name="fret-2" value="2" class="fret" data-string="2"> | |
<input type="radio" name="fret-2" value="3" class="fret" data-string="2"> | |
<input type="radio" name="fret-2" value="4" class="fret" data-string="2"> | |
<input type="radio" name="fret-2" value="5" class="fret" data-string="2"> | |
<input type="radio" name="fret-2" value="6" class="fret" data-string="2"> | |
<input type="radio" name="fret-2" value="7" class="fret" data-string="2"> | |
<input type="radio" name="fret-2" value="8" class="fret" data-string="2"> | |
<input type="radio" name="fret-2" value="9" class="fret" data-string="2"> | |
<input type="radio" name="fret-2" value="10" class="fret" data-string="2"> | |
<input type="range" class="strength" min="0" max="100" step="1" data-string="2" name="strength-2" /> | |
<button type="button" class="btn btn-primary pick" data-string="2" name="pick-2">Pick</button> | |
</div> | |
<div class="string string-1"> | |
<input type="radio" name="fret-1" checked value="0" class="fret open" data-string="1"> | |
<input type="radio" name="fret-1" value="1" class="fret" data-string="1"> | |
<input type="radio" name="fret-1" value="2" class="fret" data-string="1"> | |
<input type="radio" name="fret-1" value="3" class="fret" data-string="1"> | |
<input type="radio" name="fret-1" value="4" class="fret" data-string="1"> | |
<input type="radio" name="fret-1" value="5" class="fret" data-string="1"> | |
<input type="radio" name="fret-1" value="6" class="fret" data-string="1"> | |
<input type="radio" name="fret-1" value="7" class="fret" data-string="1"> | |
<input type="radio" name="fret-1" value="8" class="fret" data-string="1"> | |
<input type="radio" name="fret-1" value="9" class="fret" data-string="1"> | |
<input type="radio" name="fret-1" value="10" class="fret" data-string="1"> | |
<input type="range" class="strength" min="0" max="100" step="1" data-string="1" name="strength-1" /> | |
<button type="button" class="btn btn-primary pick" data-string="1" name="pick-1">Pick</button> | |
</div> | |
<div class="string string-0"> | |
<input type="radio" name="fret-0" checked value="0" class="fret open" data-string="0"> | |
<input type="radio" name="fret-0" value="1" class="fret" data-string="0"> | |
<input type="radio" name="fret-0" value="2" class="fret" data-string="0"> | |
<input type="radio" name="fret-0" value="3" class="fret" data-string="0"> | |
<input type="radio" name="fret-0" value="4" class="fret" data-string="0"> | |
<input type="radio" name="fret-0" value="5" class="fret" data-string="0"> | |
<input type="radio" name="fret-0" value="6" class="fret" data-string="0"> | |
<input type="radio" name="fret-0" value="7" class="fret" data-string="0"> | |
<input type="radio" name="fret-0" value="8" class="fret" data-string="0"> | |
<input type="radio" name="fret-0" value="9" class="fret" data-string="0"> | |
<input type="radio" name="fret-0" value="10" class="fret" data-string="0"> | |
<input type="range" class="strength" min="0" max="100" step="1" data-string="0" name="strength-0" /> | |
<button type="button" class="btn btn-primary pick" data-string="0" name="pick-0">Pick</button> | |
</div> | |
</div> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/tone/14.8.2/Tone.js" | |
integrity="sha512-mC+4OB44BnfNO5Tgllht1G+rdH82tMFKIM9pPNoQSMFqYm8PnPCffmmqTt68XpkAiJrmsQJwvFWDl8Br1o6vUw==" | |
crossorigin="anonymous"> | |
</script> | |
<script src="./banjo.js" charset="utf-8"></script> | |
<script src="./ui.js" charset="utf-8"></script> | |
</body> | |
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
.string { | |
margin: .5em 0; | |
} | |
.fret.open { | |
opacity: .1; | |
} | |
input.fret[name='fret-4'][value='1'], | |
input.fret[name='fret-4'][value='2'], | |
input.fret[name='fret-4'][value='3'], | |
input.fret[name='fret-4'][value='4'], | |
input.fret[name='fret-4'][value='5'] { | |
visibility: hidden; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const banjo = new Banjo() | |
let synths | |
const allBanjoInputs = document.querySelectorAll(".string input, .string button") | |
disableBanjo() | |
document.querySelector("#initialize").addEventListener('click', () => { | |
synths = Array(5).fill('').map(x => new Tone.Synth({ | |
envelope: { | |
attack: 0, | |
attackCurve: 'linear', | |
decay: 0.2, | |
sustain: 0.5, | |
release: 5, | |
releaseCurve: 'exponential' | |
} | |
}).toDestination()) | |
document.querySelector("#initialize").setAttribute('disabled', 'disabled') | |
enableBanjo() | |
}) | |
banjo.addEventListener('tone', tone => { | |
const stringIndex = +tone.detail.stringIndex | |
synths[stringIndex].volume.value = tone.detail.volume - 80 | |
const noteName = Tone.Frequency(tone.detail.pitch, "midi").toNote() | |
synths[stringIndex].triggerAttackRelease(noteName, 0) | |
}) | |
const pickers = document.querySelectorAll("button.pick") | |
const handlePick = (evt) => { | |
const stringIndex = +evt.target.dataset.string | |
const selector = `input[name='strength-${stringIndex}']` | |
const strength = +document.querySelector(selector).value | |
banjo.pickString(stringIndex, strength) | |
} | |
pickers.forEach(pick => pick.addEventListener('click', handlePick)) | |
const handleFret = (evt) => { | |
const stringIndex = +evt.target.dataset.string | |
const fret = +evt.target.value | |
banjo.fretString(stringIndex, fret) | |
} | |
const frets = document.querySelectorAll("input.fret") | |
frets.forEach(fret => fret.addEventListener('change', handleFret)) | |
function disableBanjo() { | |
allBanjoInputs.forEach(input => input.setAttribute('disabled', 'disabled')) | |
} | |
function enableBanjo() { | |
allBanjoInputs.forEach(input => input.removeAttribute('disabled')) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment