2020-01-11 10:02:50 +01:00
|
|
|
<!--suppress HtmlFormInputWithoutLabel -->
|
2020-01-10 13:19:55 +01:00
|
|
|
<template>
|
2020-01-10 14:42:09 +01:00
|
|
|
<div id="app">
|
2020-01-11 10:13:09 +01:00
|
|
|
<h1>noisemaker</h1>
|
2020-01-11 10:02:50 +01:00
|
|
|
<div class="channels-wrapper">
|
|
|
|
<div class="channels">
|
|
|
|
<template v-for="i in N_CHANNELS">
|
|
|
|
<Channel :name="names[i - 1]" :url="urls[i - 1]" :volume="volumes[i-1]"
|
|
|
|
:key="i"
|
|
|
|
class="channel" ref="channels"/>
|
|
|
|
</template>
|
|
|
|
</div>
|
|
|
|
<div class="channel-urls">
|
|
|
|
<div class="url-input" v-for="i in N_CHANNELS">
|
|
|
|
<label :for="`url-input-${names[i-1]}`">
|
|
|
|
{{names[i - 1]}}
|
|
|
|
</label>
|
|
|
|
<input :id="`url-input-${names[i-1]}`" type="text" v-model="urls[i - 1]"/>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div class="controls">
|
|
|
|
<button @click="start">START</button>
|
2020-01-10 14:42:09 +01:00
|
|
|
</div>
|
|
|
|
</div>
|
2020-01-10 13:19:55 +01:00
|
|
|
</template>
|
|
|
|
|
2020-01-10 14:42:09 +01:00
|
|
|
<!--suppress JSUnusedLocalSymbols, JSMethodCanBeStatic -->
|
2020-01-10 13:19:55 +01:00
|
|
|
<script lang="ts">
|
2020-01-10 14:42:09 +01:00
|
|
|
import {Component, Vue} from "vue-property-decorator";
|
|
|
|
import Channel from "@/components/Channel.vue";
|
2020-01-10 13:19:55 +01:00
|
|
|
|
|
|
|
@Component({
|
|
|
|
components: {
|
2020-01-10 14:42:09 +01:00
|
|
|
Channel,
|
2020-01-10 13:19:55 +01:00
|
|
|
},
|
|
|
|
})
|
2020-01-10 14:42:09 +01:00
|
|
|
export default class App extends Vue {
|
|
|
|
private readonly N_CHANNELS = 6;
|
2020-01-10 16:34:48 +01:00
|
|
|
private readonly LFO_PERIOD = 30_000;
|
|
|
|
private readonly LFO_DEPTH = 33;
|
|
|
|
private readonly LFO_OFFSET = 66;
|
|
|
|
|
2020-01-11 10:02:50 +01:00
|
|
|
private names = ["alpha", "beta", "gamma", "delta", "epsilon", "zeta", "eta", "theta", "iota", "kappa", "lambda", "mu"];
|
|
|
|
private urls = Array(this.N_CHANNELS).fill("");
|
2020-01-10 16:34:48 +01:00
|
|
|
private volumes = Array(this.N_CHANNELS).fill(50);
|
|
|
|
private animateVolumeStart?: Date;
|
|
|
|
private animateVolumeInterval?: number;
|
2020-01-10 14:42:09 +01:00
|
|
|
|
|
|
|
private defaultMedia = [
|
2020-01-10 16:34:48 +01:00
|
|
|
"https://www.youtube.com/watch?v=q76bMs-NwRk",
|
2020-01-10 14:42:09 +01:00
|
|
|
];
|
|
|
|
|
2020-01-11 10:02:50 +01:00
|
|
|
private mounted() {
|
|
|
|
this.defaultMedia.forEach((url, idx) => {
|
|
|
|
this.$set(this.urls, idx, url);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2020-01-10 14:42:09 +01:00
|
|
|
private start() {
|
|
|
|
(this.$refs.channels as Channel[]).forEach((channel) => {
|
|
|
|
channel.start();
|
2020-01-10 16:34:48 +01:00
|
|
|
this.animateVolume();
|
2020-01-10 14:42:09 +01:00
|
|
|
});
|
|
|
|
}
|
2020-01-10 16:34:48 +01:00
|
|
|
|
|
|
|
private animateVolume() {
|
|
|
|
clearInterval(this.animateVolumeInterval);
|
|
|
|
this.animateVolumeStart = new Date();
|
|
|
|
this.animateVolumeInterval = setInterval(() => {
|
|
|
|
if (this.animateVolumeStart) {
|
|
|
|
const delta = new Date().getTime() - this.animateVolumeStart.getTime();
|
|
|
|
|
|
|
|
this.volumes = [...Array(this.N_CHANNELS).keys()]
|
|
|
|
.map((idx) => {
|
|
|
|
const offset = idx * (1 / this.N_CHANNELS);
|
|
|
|
const progress = delta / this.LFO_PERIOD + offset;
|
|
|
|
return Math.sin(progress * 2 * Math.PI) * this.LFO_DEPTH + this.LFO_OFFSET;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}, 1000 / 60);
|
|
|
|
}
|
2020-01-10 14:42:09 +01:00
|
|
|
}
|
2020-01-10 13:19:55 +01:00
|
|
|
</script>
|
|
|
|
|
|
|
|
<style>
|
2020-01-10 16:11:49 +01:00
|
|
|
html, body {
|
2020-01-11 10:13:09 +01:00
|
|
|
font-family: monospace;
|
|
|
|
font-size: 14px;
|
2020-01-10 16:11:49 +01:00
|
|
|
padding: 0;
|
|
|
|
margin: 0;
|
|
|
|
}
|
|
|
|
|
2020-01-11 10:13:09 +01:00
|
|
|
input[type="text"] {
|
2020-01-11 10:02:50 +01:00
|
|
|
font-family: monospace;
|
2020-01-11 10:13:09 +01:00
|
|
|
padding: 4px;
|
|
|
|
background: white;
|
|
|
|
border: 1px solid black;
|
|
|
|
}
|
|
|
|
|
|
|
|
button {
|
|
|
|
background: white;
|
|
|
|
border: 1px solid black;
|
|
|
|
box-shadow: 2px 2px #272727;
|
|
|
|
font-size: 1.5rem;
|
|
|
|
}
|
|
|
|
|
|
|
|
h1 {
|
|
|
|
font-size: 20px;
|
|
|
|
margin: 1rem 0;
|
|
|
|
font-weight: normal;
|
|
|
|
text-transform: uppercase;
|
|
|
|
letter-spacing: 15px;
|
|
|
|
}
|
|
|
|
|
|
|
|
#app {
|
2020-01-10 16:11:49 +01:00
|
|
|
display: flex;
|
|
|
|
flex-direction: column;
|
|
|
|
align-items: center;
|
2020-01-11 10:02:50 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.channels-wrapper {
|
|
|
|
display: flex;
|
|
|
|
width: 80%;
|
2020-01-10 16:11:49 +01:00
|
|
|
}
|
|
|
|
|
2020-01-10 14:42:09 +01:00
|
|
|
.channels {
|
|
|
|
display: flex;
|
2020-01-11 10:13:09 +01:00
|
|
|
padding: 0 1rem;
|
2020-01-10 14:42:09 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
.channel {
|
|
|
|
margin: 0 1rem;
|
2020-01-10 13:19:55 +01:00
|
|
|
}
|
2020-01-11 10:02:50 +01:00
|
|
|
|
|
|
|
.channel-urls {
|
|
|
|
display: flex;
|
|
|
|
flex-direction: column;
|
|
|
|
justify-content: space-between;
|
|
|
|
width: 100%;
|
|
|
|
}
|
|
|
|
|
|
|
|
.url-input {
|
|
|
|
display: flex;
|
|
|
|
width: 100%;
|
|
|
|
align-items: center;
|
|
|
|
}
|
|
|
|
|
|
|
|
.url-input label {
|
|
|
|
display: inline-block;
|
|
|
|
width: 64px;
|
|
|
|
text-align: center;
|
|
|
|
}
|
|
|
|
|
|
|
|
.url-input input {
|
|
|
|
flex-grow: 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
.controls {
|
|
|
|
padding: 2rem 0;
|
|
|
|
}
|
2020-01-10 13:19:55 +01:00
|
|
|
</style>
|