Last active
March 30, 2019 00:54
-
-
Save peerreynders/c44d3bd373340d0f21fbe50435e41108 to your computer and use it in GitHub Desktop.
Web Components in Action v6: 5.2.1 Creating a Musical Instrument with Web Components and JS Modules
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
<html> | |
<head> | |
<!-- | |
file: index.html | |
https://livebook.manning.com/#!/book/web-components-in-action/chapter-5/v-6/comment-487548 | |
https://github.com/bengfarrell/webcomponentsinaction | |
--> | |
<title>Web Harp</title> | |
<link href="./main.css" type="text/css" rel="stylesheet"> | |
<link href="./csshake.min.css" type="text/css" rel="stylesheet"> | |
</head> | |
<body> | |
<button>start</button> | |
<!-- https://cdn.jsdelivr.net/npm/midi.js@0.3.1/lib/midi.min.js --> | |
<script src="./midi.min.js"></script> | |
<script type="module"> | |
import _dontCare from './components/webharp-app.mjs' | |
function startWebHarp(_event) { | |
let element = document.createElement('webharp-app') | |
element.setAttribute('strings','10') | |
document.body.removeChild(button) | |
document.body.appendChild(element) | |
} | |
const button = document.querySelector('button') | |
button.addEventListener('click', startWebHarp, {once: true}) | |
</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
// file: helpers/midiFacade.mjs | |
var promise | |
let loaded = false | |
function toLoadedState(result) { | |
loaded = result | |
} | |
/* | |
https://raw.githubusercontent.com/mudcube/MIDI.js/master/examples/soundfont/acoustic_grand_piano-ogg.js | |
*/ | |
function executor(resolve, _reject) { | |
const onsuccess = () => { | |
resolve(true) | |
} | |
MIDI.loadPlugin({ | |
soundfontUrl: './', | |
instrument: 'acoustic_grand_piano', | |
onsuccess | |
}) | |
} | |
export function loadPlugin() { | |
if (!promise) { | |
promise = new Promise(executor) | |
promise.then(toLoadedState) | |
} | |
return promise | |
} | |
export function isReady() { | |
return loaded | |
} | |
export function setVolume(...args) { | |
MIDI.setVolume(...args) | |
} | |
export function noteOn(...args) { | |
MIDI.noteOn(...args) | |
} | |
export function noteOff(...args) { | |
MIDI.noteOff(...args) | |
} |
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
// file: components/webharp-strings.mjs | |
import _dontCare from './webharp-strings.mjs' | |
function pointFromEvent({pageX, pageY}) { | |
return {x: pageX, y: pageY} | |
} | |
export default class WebHarpApp extends HTMLElement { | |
constructor() { | |
super() | |
this._onMouseMove = this._onMouseMove.bind(this) | |
} | |
connectedCallback() { | |
this.innerHTML = | |
`<webharp-strings | |
strings="${this.getAttribute('strings')}"> | |
</webharp-strings>` | |
this._stringsElement = this.querySelector('webharp-strings') | |
this.addEventListener('mousemove', this._onMouseMove) | |
} | |
_onMouseMove(event){ | |
this._stringsElement.point = pointFromEvent(event) | |
} | |
} | |
if (!customElements.get('webharp-app')) { | |
customElements.define('webharp-app', WebHarpApp) | |
} |
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
// file: components/webharp-string.js | |
import * as midi from '../helpers/midiFacade.mjs' | |
const STRUM_BASE = ['shake', 'shake-constant'] | |
const STRUM_LITTLE = [...STRUM_BASE, 'shake-little'] | |
const STRUM_NORMAL = [...STRUM_BASE, 'shake-horizontal'] | |
const STRUM_REMOVE = [...new Set([...STRUM_LITTLE, ...STRUM_NORMAL])] | |
export default class WebHarpString extends HTMLElement { | |
constructor() { | |
super() | |
this._stopStrum = this._stopStrum.bind(this) | |
} | |
connectedCallback() { | |
this.innerHTML = | |
`<div class="line"></div> | |
<style> | |
webharp-string > .line { | |
background-color: white; | |
height: 100%; | |
width: 2px; | |
} | |
</style>` | |
} | |
strum(position, power) { | |
if (this._timer) { | |
clearTimeout(this._timer) | |
this._timer = null | |
} | |
const dur = power * 10 + 250 | |
const classNames = dur < 500 ? STRUM_LITTLE : STRUM_NORMAL | |
this.classList.add(...classNames) | |
this._timer = setTimeout(this._stopStrum, dur) | |
this._playSound(position, power) | |
} | |
_stopStrum() { | |
this.classList.remove(...STRUM_REMOVE) | |
} | |
_playSound(position, power) { | |
if (!midi.isReady()) { | |
const playSound = _result => { | |
this._playSound(position, power) | |
} | |
midi.loadPlugin().then(playSound) | |
return | |
} | |
const note = 60 + position * 5 | |
midi.setVolume(0, 127) | |
midi.noteOn(0, note, power, 0) | |
midi.noteOff(0, note, 0.75) | |
} | |
} | |
if (!customElements.get('webharp-string')) { | |
customElements.define('webharp-string', WebHarpString) | |
} |
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
import _dontCare from './webharp-string.mjs' | |
function renderStrings(count) { | |
return '<webharp-string></webharp-string>'.repeat(count) | |
} | |
export default class WebHarpStrings extends HTMLElement { | |
set point(current) { | |
if(!this._stringElements) { | |
return | |
} | |
const last = this._point | |
this._point = current | |
if (!last || !current) { | |
return | |
} | |
const [xMin, xMax] = current.x > last.x ? | |
[last.x, current.x] : | |
[current.x, last.x] | |
const magnitude = xMax - xMin | |
const strumTouched = (element, index) => { | |
if (xMin <= element.offsetLeft && element.offsetLeft <= xMax) { | |
element.strum(index, magnitude) | |
} | |
} | |
this._stringElements.forEach(strumTouched) | |
} | |
connectedCallback() { | |
this.innerHTML = | |
`<div class="spacer"></div> | |
${renderStrings(this._getStringsAttribute())} | |
<style> | |
webharp-strings { | |
width: 100% | |
height: 100%; | |
display: flex; | |
} | |
webharp-strings > webharp-string, div.spacer { | |
flex: 1; | |
} | |
</style>` | |
this._stringElements = this.querySelectorAll('webharp-string') | |
} | |
_getStringsAttribute() { | |
const minCount = 1 | |
const value = this.hasAttribute('strings') ? | |
this.getAttribute('strings') : | |
'' | |
const number = value || minCount | |
return number > minCount ? number : minCount | |
} | |
} | |
if (!customElements.get('webharp-strings')) { | |
customElements.define('webharp-strings', WebHarpStrings) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment