Skip to content

Instantly share code, notes, and snippets.

@benwilhelm
Last active January 17, 2021 17:10
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save benwilhelm/3d79643a3c061be65b482506fc6af0de to your computer and use it in GitHub Desktop.
Save benwilhelm/3d79643a3c061be65b482506fc6af0de to your computer and use it in GitHub Desktop.
Banjo with UI
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 tone = this.strings[stringIndex].pick(strength)
this.dispatchEvent(new CustomEvent('tone', { detail: tone }))
return {
...tone,
stringIndex,
volume: this.muted ? tone.volume / 2 : tone.volume
}
} 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="play">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="./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()
banjo.addEventListener('tone', tone => {
console.log(tone.detail)
})
const pickButtons = document.querySelectorAll("button.pick")
const handlePick = (evt) => {
// extract the data-string attribute and parse it to a number
const stringIndex = +evt.target.dataset.string
// this is the document selector for the corresponding strength input
const selector = `input[name='strength-${stringIndex}']`
// get the value of the strenth input and parse to a number
const strength = +document.querySelector(selector).value
// Keep those UI handlers thin!
banjo.pickString(stringIndex, strength)
}
pickButtons.forEach(pick => pick.addEventListener('click', handlePick))
const frets = document.querySelectorAll("input.fret")
const handleFret = (evt) => {
const stringIndex = +evt.target.dataset.string
const fret = +evt.target.value
banjo.fretString(stringIndex, fret)
}
frets.forEach(fret => fret.addEventListener('change', handleFret))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment