Envelope generator(ADSR) with Web Audio API + Vue.js
by bc_rikko
HTML
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
<div id="app">
<envelope-generator
:width="600"
:height="200"
:attack="form.attackTime"
:decay="form.decayTime"
:sustain="form.sustainLevel"
:release="form.releaseTime">
</envelope-generator>
<form class="envelope-controller">
<div>
<label>Attack</label>
<input type="range" min="0" max="1" step="0.01" v-model.number="form.attackTime">
</div>
<div>
<label>Decay</label>
<input type="range" min="0" max="1" step="0.01" v-model.number="form.decayTime">
</div>
<div>
<label>Sustain</label>
<input type="range" min="0" max="1" step="0.01" v-model.number="form.sustainLevel">
</div>
<div>
<label>Release</label>
<input type="range" min="0" max="1" step="0.01" v-model.number="form.releaseTime">
</div>
<button type="button" @click="start">Start</button>
<button type="button" @click="stop">Stop</button>
</form>
</div>
<script type="text/x-template" id="adsr">
<svg :width="width" :height="height" xmlns="http://www.w3.org/2000/svg" baseProfile="full">
<path :d="path" stroke="#666666" stroke-width="3" fill="none"></path>
</svg>
</script>
CSS
label {
display: inline-block;
width: 70px;
}
svg {
background-color: white;
border: 1px solid rgba(6,6,6,.3);
padding: 10px;
}
JavaScript
// import [email protected]
const EnvelopeGenerator = Vue.component('envelope-generator', {
name: 'EnvelopeGenerator',
template: "#adsr",
props: {
width: {
type: Number,
default: 640
},
height: {
type: Number,
default: 480
},
attack: {
type: Number,
required: true,
validaor: v => 0 <= v && v <= 1
},
decay: {
type: Number,
required: true,
validaor: v => 0 <= v && v <= 1
},
sustain: {
type: Number,
required: true,
validaor: v => 0 <= v && v <= 1
},
release: {
type: Number,
required: true,
validaor: v => 0 <= v && v <= 1
}
},
data () {
return {
path: ''
}
},
mounted() {
this.draw();
},
watch: {
attack: function () { this.draw(); },
decay: function () { this.draw(); },
sustain: function () { this.draw(); },
release: function () { this.draw(); }
},
methods: {
draw() {
const wRetio = this.width / 4;
const hRetio = this.height / 1;
const paths = [];
let x, y;
x = y = 0;
// attack
x = this.attack * wRetio;
y = 0;
paths.push(`${x} ${y}`);
// decay
x += this.decay * wRetio;
y = this.height - this.sustain * hRetio;
paths.push(`${x} ${y}`);
// sustain
x += 1 * wRetio;
paths.push(`${x} ${y}`);
// release
x += this.release * wRetio;
y = this.height;
paths.push(`${x} ${y}`);
this.path = `M0 ${this.height},` + paths.join(',');
}
}
});
new Vue({
el: '#app',
components: { EnvelopeGenerator },
data() {
return {
form: {
attackTime: 0.5,
decayTime: 0.3,
sustainLevel: 0.5,
releaseTime: 1.0
},
ctx: new AudioContext(),
osc: null,
adsr: null
}
},
methods: {
start() {
this.osc = this.ctx.createOscillator();
this.adsr = this.ctx.createGain();
//...