noise-maker/src/App.vue

184 lines
4.4 KiB
Vue
Raw Permalink Normal View History

import {PlayingState} from "@/common";
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]" :playing="playing"
:key="i" @loadingState="(state) => {$set(states, i - 1, state)}"
2020-01-11 10:02:50 +01:00
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>
2020-01-12 12:20:00 +01:00
<button class="channel-urls-clear-button" @click="urls = Array(N_CHANNELS).fill('')">CLEAR</button>
2020-01-11 10:02:50 +01:00
</div>
</div>
<div class="controls">
<button @click="start" :disabled="!startEnabled">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";
import {LoadingState, PlayingState} from "@/common";
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;
private playing: PlayingState = PlayingState.STOPPED;
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("");
private volumes = Array(this.N_CHANNELS).fill(100);
private states = Array(this.N_CHANNELS).fill(LoadingState.UNLOADED);
2020-01-10 16:34:48 +01:00
private animateVolumeStart?: Date;
private animateVolumeInterval?: number;
2020-01-10 14:42:09 +01:00
private readonly defaultMedia = [
2020-01-11 10:16:05 +01:00
"https://www.youtube.com/watch?v=jX6kn9_U8qk",
"https://www.youtube.com/watch?v=E77jmtut1Zc",
"https://youtu.be/OW7TH2U4hps"
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.playing = PlayingState.PLAYING;
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);
}
private get startEnabled() {
return this.states.every((state) => state !== LoadingState.LOADING);
}
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-12 12:15:16 +01:00
flex-wrap: wrap;
2020-01-10 16:11:49 +01:00
}
2020-01-10 14:42:09 +01:00
.channels {
display: flex;
2020-01-12 12:15:16 +01:00
padding: 0 1rem 1rem 0;
justify-content: space-between;
flex-grow: 1;
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;
2020-01-12 12:20:00 +01:00
align-items: center;
2020-01-11 10:02:50 +01:00
justify-content: space-between;
2020-01-12 12:15:16 +01:00
flex-grow: 1;
2020-01-11 10:02:50 +01:00
}
2020-01-12 12:20:00 +01:00
.channel-urls-clear-button {
max-width: 5em;
}
2020-01-11 10:02:50 +01:00
.url-input {
display: flex;
width: 100%;
align-items: center;
2020-01-12 12:15:16 +01:00
margin: .2rem 0;
2020-01-11 10:02:50 +01:00
}
.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>