Skip to content

Instantly share code, notes, and snippets.

@benwilhelm
Created January 19, 2021 03:45
Show Gist options
  • Save benwilhelm/77c47e8b0d828969cedbffaad3a0357b to your computer and use it in GitHub Desktop.
Save benwilhelm/77c47e8b0d828969cedbffaad3a0357b to your computer and use it in GitHub Desktop.
Banjo with UI and Sound
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
}
}
}
<!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>
.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;
}
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