Last active
April 13, 2019 00:49
-
-
Save kefavn/510381aeec7efd13a968569b9fd3f814 to your computer and use it in GitHub Desktop.
Web Audio
This file contains hidden or 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 Component from '@ember/component'; | |
import { action, computed } from '@ember-decorators/object'; | |
import { className, classNames } from '@ember-decorators/component'; | |
import { inject as service } from '@ember-decorators/service'; | |
@classNames('x-audio-out') | |
export default class extends Component { | |
@service audio | |
@className | |
@computed('audio.isPlaying') | |
get active() { | |
return this.audio.isPlaying; | |
} | |
didInsertElement() { | |
this.elementUpdated(this.element); | |
} | |
click() { | |
if (this.audio.isPlaying) { | |
return this.audio.pause(); | |
} | |
return this.audio.play(); | |
} | |
}; |
This file contains hidden or 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 Component from '@ember/component'; | |
import EmberObject from '@ember/object'; | |
import { connectElements } from 'app/utils/svg-connect'; | |
import { computed, action } from '@ember-decorators/object'; | |
import { className, classNames } from '@ember-decorators/component'; | |
import { inject as service } from '@ember-decorators/service'; | |
@classNames('x-connector') | |
export default class extends Component { | |
constructor() { | |
super(...arguments); | |
this.stateMap = EmberObject.create({}); | |
this.stateElementMap = EmberObject.create({}); | |
} | |
didRender() { | |
this.updateSVG(); | |
} | |
updateSVG() { | |
const controls = this.output; | |
if (!this.output) { | |
return; | |
} | |
const container = $(this.element.querySelector('.svgContainer')); | |
const s = $(this.element.querySelector('.svg1')); | |
const $controls = $(this.output); | |
Object.keys(this.stateMap).forEach(label => { | |
const element = $(`#${this.elementId}${label}`); | |
if (element.length) { | |
const el = this.stateElementMap[label]; | |
connectElements(container, s, element, $(el), $controls); | |
} | |
}); | |
} | |
@action | |
addInput(element, label) { | |
this.set(`stateMap.${label}`, true); | |
this.set(`stateElementMap.${label}`, element); | |
} | |
@action | |
addOutput(element) { | |
this.set('output', element); | |
} | |
@action | |
updateState(label, state) { | |
this.set(`stateMap.${label}`, state); | |
} | |
}; |
This file contains hidden or 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 Ember from 'ember'; | |
import Component from '@ember/component'; | |
import { computed } from '@ember-decorators/object'; | |
import { htmlSafe } from '@ember/template'; | |
import { restartableTask } from 'ember-concurrency-decorators'; | |
import { debounce } from '@ember/runloop'; | |
export default class extends Component { | |
constructor() { | |
super(...arguments); | |
this._prevValue = 0; | |
this.dragResistance = 3; | |
this.wheelResistance = 100; | |
this.integerOnly = true; | |
this._handlers = { | |
inputChange: this.handleInputChange.bind(this), | |
touchStart: this.handleTouchStart.bind(this), | |
touchMove: this.handleTouchMove.bind(this), | |
touchEnd: this.handleTouchEnd.bind(this), | |
touchCancel: this.handleTouchCancel.bind(this), | |
mouseDown: this.handleMouseDown.bind(this), | |
mouseMove: this.handleMouseMove.bind(this), | |
mouseUp: this.handleMouseUp.bind(this), | |
mouseWheel: this.handleMouseWheel.bind(this), | |
focus: this.handleFocus.bind(this), | |
blur: this.handleBlur.bind(this), | |
}; | |
} | |
didInsertElement() { | |
super.didInsertElement(...arguments); | |
this.element.addEventListener('wheel', this._handlers.mouseWheel); | |
} | |
@computed('knobOffset') | |
get gripStyle() { | |
const deg = -132 + this.knobOffset * 264; | |
return htmlSafe(`transform: translate(-50%, -50%) rotate(${deg}deg);`); | |
} | |
@computed('value', 'min', 'max') | |
get knobOffset() { | |
const min = this.min; | |
const max = this.max; | |
// console.log(`V: ${this.value} Mi: ${min} ma: ${max}`); | |
return Math.abs(this.value - this.min) / (this.max - this.min); | |
} | |
@computed('knobOffset') | |
get gripStrokeOffset() { | |
const offset = (1 - this.knobOffset) * 184; | |
return htmlSafe(`stroke-dashoffset: ${offset}`); | |
} | |
mouseDown(event) { | |
return this.handleMouseDown(event); | |
} | |
touchStart(event) { | |
return this.handleTouchStart(event); | |
} | |
updateValue(value) { | |
this.onChange(value); | |
} | |
handleMouseDown(evt) { | |
// console.log('mouse down'); | |
this.clearDrag(); | |
evt.preventDefault(); | |
this._activeDrag = true; | |
this.startDrag(evt.clientY); | |
// drag update/end listeners | |
document.body.addEventListener('mousemove', this._handlers.mouseMove); | |
document.body.addEventListener('mouseup', this._handlers.mouseUp); | |
} | |
// handlers | |
handleInputChange(evt) { | |
// console.log('input change'); | |
this.updateToInputValue(); | |
} | |
handleTouchStart(evt) { | |
// console.log('touch start'); | |
this.clearDrag(); | |
evt.preventDefault(); | |
var touch = evt.changedTouches.item(evt.changedTouches.length - 1); | |
this._activeDrag = touch.identifier; | |
this.startDrag(touch.clientY); | |
// drag update/end listeners | |
document.body.addEventListener('touchmove', this._handlers.touchMove); | |
document.body.addEventListener('touchend', this._handlers.touchEnd); | |
document.body.addEventListener('touchcancel', this._handlers.touchCancel); | |
} | |
handleTouchMove(evt) { | |
// console.log('touch move'); | |
var activeTouch = this.findActiveTouch(evt.changedTouches); | |
if (activeTouch) { | |
this.updateDrag(activeTouch.clientY); | |
} else if (!this.findActiveTouch(evt.touches)) { | |
this.clearDrag(); | |
} | |
} | |
handleTouchEnd(evt) { | |
// console.log('touch end'); | |
var activeTouch = this.findActiveTouch(evt.changedTouches); | |
if (activeTouch) { | |
this.finalizeDrag(activeTouch.clientY); | |
} | |
} | |
handleTouchCancel(evt) { | |
// console.log('touch cancel'); | |
if (this.findActiveTouch(evt.changedTouches)) { | |
this.clearDrag(); | |
} | |
} | |
handleMouseDown(evt) { | |
// console.log('mouse down'); | |
this.clearDrag(); | |
evt.preventDefault(); | |
this._activeDrag = true; | |
this.startDrag(evt.clientY); | |
// drag update/end listeners | |
document.body.addEventListener('mousemove', this._handlers.mouseMove); | |
document.body.addEventListener('mouseup', this._handlers.mouseUp); | |
} | |
handleMouseMove(evt) { | |
// console.log('mouse move'); | |
if (evt.buttons&1) { | |
this.updateDrag(evt.clientY); | |
} else { | |
this.finalizeDrag(evt.clientY); | |
} | |
} | |
handleMouseUp(evt) { | |
// console.log('mouse up'); | |
this.finalizeDrag(evt.clientY); | |
} | |
handleMouseWheel(evt) { | |
// console.log('mouse wheel'); | |
this.element.focus(); | |
this.clearDrag(); | |
this._prevValue = parseFloat(this.value); | |
this.updateFromDrag(evt.deltaY, this.wheelResistance); | |
} | |
handleDoubleClick(evt) { | |
// console.log('double click'); | |
this.clearDrag(); | |
this.updateValue(this.initial); | |
this.updateToInputValue(); | |
} | |
handleFocus(evt) { | |
// console.log('focus on'); | |
//this._container.classList.add('focus-active'); | |
} | |
handleBlur(evt) { | |
// console.log('focus off'); | |
//this._container.classList.remove('focus-active'); | |
} | |
// dragging | |
startDrag(yPosition) { | |
this._dragStartPosition = yPosition; | |
this._prevValue = parseFloat(this.value); | |
this.element.focus(); | |
} | |
updateDrag(yPosition) { | |
var diff = yPosition - this._dragStartPosition; | |
this.updateFromDrag(diff, this.dragResistance); | |
} | |
finalizeDrag(yPosition) { | |
var diff = yPosition - this._dragStartPosition; | |
this.updateFromDrag(diff, this.dragResistance); | |
this.clearDrag(); | |
} | |
clearDrag() { | |
this._activeDrag = false; | |
// clean up event listeners | |
document.body.removeEventListener('mousemove', this._handlers.mouseMove); | |
document.body.removeEventListener('mouseup', this._handlers.mouseUp); | |
document.body.removeEventListener('touchmove', this._handlers.touchMove); | |
document.body.removeEventListener('touchend', this._handlers.touchEnd); | |
document.body.removeEventListener('touchcancel', this._handlers.touchCancel); | |
} | |
updateToInputValue() { | |
var normVal = this.normalizeValue(parseFloat(this._input.value)); | |
this.updateVisuals(normVal); | |
} | |
updateFromDragTask(dragAmount, resistance) { | |
let value = this._prevValue - (dragAmount/resistance); | |
console.log(`DART: ${dragAmount} Res: ${resistance} V: ${value}`); | |
if (this.integerOnly) { | |
value = parseInt(value); | |
} | |
var clampedValue = this.clampValue(value); | |
this.updateValue(clampedValue); | |
} | |
updateFromDrag(dragAmount, resistance) { | |
//debounce(this, this.updateFromDragTask, dragAmount, resistance, 16); | |
this.updateFromDragTask(dragAmount, resistance); | |
} | |
// utils | |
clampValue(val) { | |
var min = parseFloat(this.min); | |
var max = parseFloat(this.max); | |
return Math.min(Math.max(val, min), max); | |
} | |
normalizeValue(val) { | |
var min = parseFloat(this.min); | |
var max = parseFloat(this.max); | |
return (val-min)/(max-min); | |
} | |
findActiveTouch(touchList) { | |
var i, len, touch; | |
for (i=0, len=touchList.length; i<len; i++) | |
if (this._activeDrag === touchList.item(i).identifier) | |
return touchList.item(i); | |
return null; | |
} | |
}; |
This file contains hidden or 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 Ember from 'ember'; | |
import { inject as service } from '@ember/service'; | |
import { computed } from '@ember/object'; | |
import { Oscillator } from 'app/utils/audio'; | |
export default Ember.Component.extend({ | |
classNames: ['panel'], | |
active: true, | |
audio: service(), | |
init() { | |
this._super(...arguments); | |
const audioContext = this.audio.audioContext; | |
const oscillator = new Oscillator({ out: audioContext.destination, audioContext }); | |
this.audio.addOscillator(oscillator); | |
this.oscillator = oscillator; | |
if (this.startingNoteOffset) { | |
this.oscillator.changeNoteOffset(this.startingNoteOffset); | |
} | |
if (this.startingGain) { | |
this.oscillator.changeGain(this.startingGain); | |
} | |
}, | |
didInsertElement() { | |
if (this.elementUpdated) { | |
this.elementUpdated(this.element.querySelector('.connectable'), this.label); | |
} | |
}, | |
actions: { | |
changeNoteOffset(offset) { | |
this.oscillator.changeNoteOffset(offset); | |
}, | |
changeLowOscillatorType(type) { | |
this.oscillator.changeWaveType(type); | |
}, | |
changeGain(gain) { | |
this.oscillator.changeGain(gain); | |
}, | |
setActive() { | |
this.toggleProperty('active'); | |
this.updateState(this.label, this.active); | |
} | |
} | |
}); |
This file contains hidden or 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 Component from '@ember/component'; | |
import EmberObject from '@ember/object'; | |
import { connectElements } from 'app/utils/svg-connect'; | |
import { computed, action } from '@ember-decorators/object'; | |
import { className, classNames } from '@ember-decorators/component'; | |
import { inject as service } from '@ember-decorators/service'; | |
@classNames('x-wave-visualizer') | |
export default class extends Component { | |
@service audio | |
@computed() | |
get vElement() { | |
return this.element.querySelectorFor('.visualizer'); | |
} | |
@computed('audio.analyser') | |
get visualizer() { | |
const analyser = this.audio.analyser; | |
} | |
}; |
This file contains hidden or 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 Mixin from '@ember/object/mixin'; | |
import { inject as service } from '@ember-decorators/service'; | |
export default class AudioNode extends Mixin { | |
@service audio; | |
node = null; | |
input = null; | |
output = null; | |
group = null; | |
constructor() { | |
super(...arguments); | |
if (!this.node || !this.group) { | |
return console.error('Missing node or group'); | |
} | |
if (!this.input && !this.output) { | |
return console.error('Input/Output missing'); | |
} | |
this.group.nodes.addObject(this.node); | |
} | |
willDestroyElement() { | |
this.group.nodes.removeObject(this.node); | |
} | |
}; |
This file contains hidden or 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 Ember from 'ember'; | |
import { computed } from '@ember/object'; | |
import Evented from '@ember/object/evented'; | |
import { Note } from 'app/utils/frequency'; | |
export default Ember.Service.extend(Evented, { | |
isPlaying: false, | |
oscillators: [], | |
audioContext: computed(function() { | |
return new (window.AudioContext || window.webkitAudioContext)(); | |
}), | |
addOscillator(oscillator) { | |
this.oscillators.pushObject(oscillator); | |
}, | |
play() { | |
this.set('isPlaying', true); | |
console.log(this.oscillators); | |
this.oscillators.forEach(oscillator => oscillator.on()); | |
}, | |
pause() { | |
this.oscillators.forEach(oscillator => oscillator.off()); | |
this.set('isPlaying', false); | |
} | |
}); |
This file contains hidden or 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 { | |
--primary-color: #E4E8EA; | |
--background-color: #181B1C; | |
--color-dead: rgba(0,0,0,0.2); | |
--dark: rgba(0,0,0,0.3); | |
--darker: rgba(0,0,0,0.5); | |
} | |
body { | |
margin: 2em 2em; | |
font-family: monospace, Helvetica, Arial, sans-serif; | |
background-color: var(--background-color); | |
color: var(--primary-color); | |
} | |
button { | |
height: 24px; | |
} | |
.container { | |
margin: 2em 2em; | |
border-radius: 10px; | |
border-color: var(--primary-color); | |
border-width: 3px; | |
border-style: solid; | |
} | |
.container > h2 { | |
font-weight: 400; | |
padding-left: 30px; | |
padding-right: 30px; | |
margin-top: 0px; | |
transform: translate(0, -60%); | |
margin-bottom: 10px; | |
background-color: var(--background-color); | |
} | |
.section { | |
padding-left: 20px; | |
padding-right: 20px; | |
width: 100%; | |
} | |
.grid { | |
display: grid; | |
grid-template-rows: repeat(1, 480px); | |
grid-template-columns: repeat(3, 140px); | |
grid-column-gap: 20px; | |
grid-row-gap: 20px; | |
} | |
.grid-item { | |
z-index: 2; | |
grid-column-start: 1; | |
grid-column-end: 10; | |
grid-row-start: 1; | |
grid-row-end: 10; | |
} | |
.item-container { | |
width: 300px; | |
height: 300px; | |
border-radius: 5px; | |
border: 3px dashed #999; | |
} |
This file contains hidden or 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
.color-dead { | |
--highlight-color: var(--color-dead); | |
} | |
.white { | |
--highlight-color: white; | |
} | |
.orange { | |
--highlight-color: hsl(32, 95%, 50%); | |
} | |
.green { | |
--highlight-color: hsl(144, 95%, 38%); | |
} | |
.pink { | |
--highlight-color: hsl(313, 95%, 50%); | |
} | |
.blue { | |
--highlight-color: hsl(240, 95%, 50%); | |
} |
This file contains hidden or 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
.page { | |
display: block; | |
position: relative; | |
} | |
.center { | |
display: flex; | |
align-items: center; | |
justify-content: center; | |
flex-direction: column; | |
} | |
.center.row { | |
flex-direction: row; | |
} | |
.inline.center { | |
display: inline-flex; | |
} |
This file contains hidden or 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
.x-audio-out { | |
font-size: 48px; | |
border-radius: 50%; | |
text-align: center; | |
line-height: 80px; | |
width: 80px; | |
height: 80px; | |
opacity: 0.5; | |
user-select: none; | |
cursor: pointer; | |
background-color: var(--darker); | |
transition: 0.3s cubic-bezier(0, 0, 0.24, 1); | |
border: 2px solid var(--primary-color); | |
} | |
.x-audio-out.active { | |
opacity: 1; | |
background-color: var(--highlight-color, var(--primary-color)); | |
transition: 0s; | |
animation: pulse-outer 2s linear infinite; | |
} | |
@keyframes pulse-outer { | |
0% { | |
box-shadow: 0 0 0 2px rgba(228,232,234, 0) | |
} | |
40% { | |
box-shadow: 0 0 0 4px rgba(228,232,234, 0.2) | |
} | |
70% { | |
box-shadow: 0 0 0 10px rgba(228,232,234, 0.12) | |
} | |
100% { | |
box-shadow: 0 0 0 12px rgba(228,232,234, 0) | |
} | |
} |
This file contains hidden or 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
.svgContainer { | |
z-index: -10; | |
position:absolute; | |
top: 0; | |
left: 0; | |
} | |
.svgContainer path { | |
stroke: white; | |
stroke-dashoffset: 100%; | |
stroke-dasharray: 100% 100%; | |
opacity: 0.25; | |
transition: 0.4s cubic-bezier(0, 0, 0.24, 1); | |
} | |
.svgContainer path.on { | |
stroke-dashoffset: 0%; | |
stroke: var(--primary-color); | |
opacity: 0.5; | |
} |
This file contains hidden or 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
.knob-container { | |
margin: 10px; | |
color: var(--highlight-color, white); | |
position: relative; | |
width: 100px; | |
height: 100px; | |
} | |
.knob-handle svg { | |
pointer-events: none; | |
position: absolute; | |
stroke-width: 8; | |
stroke-dasharray: 184 184; | |
stroke-width: 5; | |
stroke-color: currentColor; | |
} | |
.knob-handle svg path { | |
transition: 0.3s cubic-bezier(0, 0, 0.24, 1); | |
} | |
.knob-handle svg path.colored { | |
stroke: var(--highlight-color, white); | |
} | |
.knob-handle svg path.track { | |
stroke: rgba(255,255,255, 0.1); | |
} | |
.knob-grip { | |
position: absolute; | |
top: 50%; | |
left: 50%; | |
z-index: 5; | |
height: 82px; | |
width: 82px; | |
transition: 0.3s cubic-bezier(0, 0, 0.24, 1); | |
} | |
.knob-grip::after { | |
height: 20px; | |
width: 2px; | |
border-radius: 4px; | |
background-color: var(--highlight-color, white); | |
content: ""; | |
position: absolute; | |
top: -1px; | |
left: 50%; | |
right: ; | |
bottom: ; | |
transform: translateX(-50%); | |
} | |
.knob-value { | |
position: absolute; | |
top: 50%; | |
left: 50%; | |
margin-top: -3px; | |
transform: translate(-50% , -50%); | |
} |
This file contains hidden or 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
.inner-panel .panel-title { | |
padding-bottom: 10px; | |
margin-bottom: 0px; | |
user-select: none; | |
cursor: pointer; | |
} | |
.inner-panel { | |
position: relative; | |
border-radius: 3px; | |
background-color: #2c2d2f; | |
} | |
.inner-panel > * { | |
width: 100%; | |
margin: 10px; | |
margin-top: 0px; | |
} | |
.inner-panel .panel-body, | |
.inner-panel .panel-title { | |
margin-top: 10px; | |
} | |
.inner-panel h4 { | |
border-bottom: 4px solid #181b1c; | |
} | |
.inner-panel .panel-row { | |
margin-bottom: 10px; | |
margin-top: 10px; | |
} | |
.panel-state { | |
position: absolute; | |
top: 5px; | |
left: 5px; | |
border-radius: 50%; | |
width: 8px; | |
height: 8px; | |
background-color: rgba(0,0,0,0.3); | |
} | |
.panel-state.active { | |
background-color: var(--highlight-color, var(--primary-color)); | |
} |
This file contains hidden or 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
{ | |
"version": "0.15.1", | |
"EmberENV": { | |
"FEATURES": {} | |
}, | |
"options": { | |
"use_pods": false, | |
"enable-testing": false | |
}, | |
"dependencies": { | |
"jquery": "https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.js", | |
"ember": "3.4.3", | |
"ember-template-compiler": "3.4.3", | |
"ember-testing": "3.4.3" | |
}, | |
"addons": { | |
"ember-data": "3.4.2", | |
"ember-decorators": "2.0.0", | |
"ember-truth-helpers": "2.1.0", | |
"ember-contextual-states": "0.1.6", | |
"ember-animated": "0.5.1", | |
"ember-decorators": "5.2.0", | |
"ember-concurrency": "0.9.0", | |
"ember-concurrency-decorators": "0.6.0" | |
} | |
} |
This file contains hidden or 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
export default class { | |
nodes = []; | |
} |
This file contains hidden or 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 { Note } from 'app/utils/frequency'; | |
import { computed } from '@ember-decorators/object'; | |
import EmberObject from '@ember/object'; | |
export class Oscillator extends EmberObject { | |
startedBefore = false; | |
active = false; | |
oscillatorNode = null; | |
waveType = 'sine'; | |
out = null; | |
audioContext = null; | |
noteOffset = 0; | |
@computed('noteOffset') | |
get note() { | |
return new Note(this.noteOffset); | |
} | |
@computed('note') | |
get frequency() { | |
return this.note.frequency; | |
} | |
constructor({ gain, waveType, out, audioContext }) { | |
super(...arguments); | |
this.gain = gain || 50; | |
this.waveType = waveType || 'sine'; | |
this.out = out; | |
this.audioContext = audioContext; | |
this.setupOscillator(); | |
} | |
setupOscillator() { | |
this.oscillatorNode = this.createOscillatorNode(); | |
this.gainNode = this.createGainNode(); | |
this.changeGain(this.gain); | |
this.oscillatorNode.connect(this.gainNode); | |
this.gainNode.connect(this.out); | |
} | |
createGainNode() { | |
return this.audioContext.createGain(); | |
} | |
createOscillatorNode() { | |
const oscillator = this.audioContext.createOscillator(); | |
oscillator.type = this.waveType; | |
oscillator.frequency.setValueAtTime(this.frequency, this.audioContext.currentTime); | |
return oscillator; | |
} | |
changeWaveType(type) { | |
this.set('waveType', type); | |
console.log(type); | |
if (this.active) { | |
this.off(); | |
this.on(); | |
} else { | |
this.setupOscillator(); | |
} | |
} | |
changeNoteOffset(offset) { | |
this.set('noteOffset', offset); | |
this.oscillatorNode.frequency.exponentialRampToValueAtTime(this.frequency, this.audioContext.currentTime + 0.001) | |
} | |
changeGain(gain) { | |
this.set('gain', gain); | |
this.gainNode.gain.value = gain / 100; | |
} | |
on() { | |
this.active = true; | |
this.oscillatorNode.start(); | |
} | |
off() { | |
this.oscillatorNode.stop(); | |
this.gainNode.disconnect(); | |
this.oscillatorNode.disconnect(); | |
this.setupOscillator(); | |
this.active = false; | |
} | |
} |
This file contains hidden or 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 LETTERS = ['A', 'A½', 'B', 'C', 'C½', 'D', 'D½', 'E', 'F', 'F½', 'G', 'G½']; | |
export class Note { | |
constructor(offsetFromMiddleA) { | |
this.noteOffset = parseInt(offsetFromMiddleA); | |
} | |
get frequency() { | |
return 440 * Math.pow(2, this.noteOffset/12); | |
} | |
get frequencyDisplay() { | |
return this.frequency.toFixed(2); | |
} | |
get octave() { | |
return 4 + parseInt(this.noteOffset/12); | |
} | |
get letterOffset() { | |
return (120 +this.noteOffset + 120) % 12; | |
} | |
get letter() { | |
return LETTERS[this.letterOffset]; | |
} | |
toString() { | |
return `${this.letter}${String.fromCharCode(this.octave + 8320)}`; | |
} | |
} |
This file contains hidden or 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
function signum(x) { | |
return (x < 0) ? -1 : 1; | |
} | |
function absolute(x) { | |
return (x < 0) ? -x : x; | |
} | |
function drawPath(svg, path, startX, startY, endX, endY) { | |
// get the path's stroke width (if one wanted to be really precize, one could use half the stroke size) | |
var stroke = parseFloat(path.attr("stroke-width")); | |
// check if the svg is big enough to draw the path, if not, set heigh/width | |
if (svg.attr("height") < endY) svg.attr("height", endY); | |
if (svg.attr("width" ) < (startX + stroke) ) svg.attr("width", (startX + stroke)); | |
if (svg.attr("width" ) < (endX + stroke) ) svg.attr("width", (endX + stroke)); | |
var deltaX = (endX - startX) * 0.15; | |
var deltaY = (endY - startY) * 0.15; | |
// for further calculations which ever is the shortest distance | |
var delta = deltaY < absolute(deltaX) ? deltaY : absolute(deltaX); | |
// set sweep-flag (counter/clock-wise) | |
// if start element is closer to the left edge, | |
// draw the first arc counter-clockwise, and the second one clock-wise | |
var arc1 = 0; var arc2 = 1; | |
if (startX > endX) { | |
arc1 = 1; | |
arc2 = 0; | |
} | |
// draw tha pipe-like path | |
// 1. move a bit down, 2. arch, 3. move a bit to the right, 4.arch, 5. move down to the end | |
path.attr("d", "M" + startX + " " + startY + | |
" V" + (startY + delta) + | |
" A" + delta + " " + delta + " 0 0 " + arc1 + " " + (startX + delta*signum(deltaX)) + " " + (startY + 2*delta) + | |
" H" + (endX - delta*signum(deltaX)) + | |
" A" + delta + " " + delta + " 0 0 " + arc2 + " " + endX + " " + (startY + 3*delta) + | |
" V" + endY ); | |
} | |
export function connectElements(svgContainer, svg, path, startElem, endElem) { | |
//console.log(`c: ${svgContainer} s: ${svg} p: ${path} se ${startElem} e ${endElem}`); | |
// if first element is lower than the second, swap! | |
if(startElem.offset().top > endElem.offset().top){ | |
var temp = startElem; | |
startElem = endElem; | |
endElem = temp; | |
} | |
// get (top, left) corner coordinates of the svg container | |
var svgTop = svgContainer.offset().top; | |
var svgLeft = svgContainer.offset().left; | |
// get (top, left) coordinates for the two elements | |
var startCoord = startElem.offset(); | |
var endCoord = endElem.offset(); | |
// calculate path's start (x,y) coords | |
// we want the x coordinate to visually result in the element's mid point | |
var startX = startCoord.left + 0.5*startElem.outerWidth() - svgLeft; // x = left offset + 0.5*width - svg's left offset | |
var startY = startCoord.top + startElem.outerHeight() - svgTop; // y = top offset + height - svg's top offset | |
// calculate path's end (x,y) coords | |
var endX = endCoord.left + 0.5*endElem.outerWidth() - svgLeft; | |
var endY = endCoord.top - svgTop; | |
// call function for drawing the path | |
drawPath(svg, path, startX, startY, endX, endY); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment