This commit is contained in:
Klemek
2024-10-18 16:01:49 +02:00
parent 866e2aaa28
commit 3dc6034ae0
30 changed files with 164 additions and 26 deletions
+2
View File
@@ -0,0 +1,2 @@
normalize: ./sounds/*.wav
for file in $^; do ffmpeg-normalize $${file} --force --normalization-type peak --target-level -1 --output $${file}; done
+38 -9
View File
@@ -18,16 +18,45 @@
<body> <body>
<main id="app" style="display:none"> <main id="app" style="display:none">
<h1>Startle Machine</h1> <h1>Startle Machine</h1>
<p>Generates silence occasionally broken up by random sound effects.<br>Click the button below and leave this tab to start the prank.</p>
<button class="start" :disabled="started" @click="start">{{ started ? 'You can leave this tab now...' : 'Start the prank' }}</button>
<br> <br>
<button @click="start">Start</button> <h2>Configuration</h2>
<button @click="selectAll">Select all</button> <table class="config">
<button @click="deselectAll">Deselect all</button> <colgroup>
<br> <col style="width: 25%">
<div style="display:flex;flex-flow: row wrap;"> <col>
<div style="width: 33%; padding: .5em;" v-for="sound in sounds"> <col style="width: 25%">
<input v-bind:id="`active-${sound.id}`" type="checkbox" v-model="sound.active">&nbsp; </colgroup>
<label v-bind:for="`active-${sound.id}`">{{ sound.name }}</label> <tr>
</div> <td><label for="minDelay">Minimum delay:</label></td>
<td><input id="minDelay" type="range" v-model="minDelay" min="1" max="120" /></td>
<td>{{ minDelay }} minutes</td>
</tr>
<tr>
<td><label for="maxDelay">Maximum delay:</label></td>
<td><input id="maxDelay" type="range" v-model="maxDelay" min="1" max="120" /></td>
<td>{{ maxDelay }} minutes</td>
</tr>
<tr>
<td><label for="volume">Volume:</label></td>
<td><input id="volume" type="range" v-model="volume" min="0" max="100" /></td>
<td>{{ volume }}%</td>
</tr>
<tr>
<td colspan="3">
<button :class="randomPitch ? '' : 'inactive'" @click="randomPitch = !randomPitch">Random pitch: {{ randomPitch ? '✅' : '❌' }}</button>
</td>
</tr>
<tr>
<td colspan="3">
<button :class="hidePrank ? '' : 'inactive'" @click="hidePrank = !hidePrank">Blank page on start: {{ hidePrank ? '✅' : '❌' }}</button>
</td>
</tr>
</table>
<h3>Sounds <small><a href="#" @click="selectAll">Select all</a> - <a href="#" @click="unselectAll">Unselect all</a></small></h3>
<div class="sound-container">
<button v-for="sound in sounds" :class="sound.active ? '' : 'inactive'" @click="sound.active = !sound.active">{{ sound.name }}</button>
</div> </div>
<br> <br>
<small><a href="https://github.com/klemek" target="_blank">@klemek</a> - <a href="https://github.com/klemek/startle-machine" target="_blank">Github Repository</a> - 2024</small> <small><a href="https://github.com/klemek" target="_blank">@klemek</a> - <a href="https://github.com/klemek/startle-machine" target="_blank">Github Repository</a> - 2024</small>
+29 -9
View File
@@ -47,19 +47,32 @@ let app = {
data() { data() {
return { return {
sounds: [], sounds: [],
started: false,
minDelay: 15,
maxDelay: 45,
volume: 50,
randomPitch: true,
hidePrank: true,
}; };
}, },
computed: { computed: {},
currentYear() { watch: {
return new Date().getFullYear(); minDelay() {
this.maxDelay = Math.max(this.minDelay, this.maxDelay);
},
maxDelay() {
this.minDelay = Math.min(this.minDelay, this.maxDelay);
}, },
}, },
methods: { methods: {
showApp() { showApp() {
document.getElementById("app").setAttribute("style", ""); document.getElementById("app").setAttribute("style", "");
}, },
hideApp() {
document.getElementById("app").setAttribute("style", "display:none");
document.body.setAttribute("style", "background: none");
},
onBlur() { onBlur() {
// TODO document.getElementById("app").setAttribute("style", "display:none");
// TODO // TODO
}, },
onFocus() { onFocus() {
@@ -74,24 +87,30 @@ let app = {
}); });
}, },
start() { start() {
if (!this.started) {
const audioCtx = new AudioContext(); const audioCtx = new AudioContext();
this.sounds.forEach(sound => { this.sounds.forEach(sound => {
const source = audioCtx.createMediaElementSource(sound.audio); const source = audioCtx.createMediaElementSource(sound.audio);
source.connect(audioCtx.destination); source.connect(audioCtx.destination);
}); });
this.trigger(); this.trigger();
if (this.hidePrank) {
this.hideApp();
}
}
}, },
trigger() { trigger() {
const time = random.int(30, 300); this.started = true;
console.log(`Next in ${time} seconds`); const time = random.int(this.minDelay, this.maxDelay);
setTimeout(this.playRandomSound, time * 1000); console.log(`Next in ${time} minutes`);
setTimeout(this.playRandomSound, time * 60 * 1000);
}, },
selectAll() { selectAll() {
this.sounds.forEach(sound => { this.sounds.forEach(sound => {
sound.active = true; sound.active = true;
}); });
}, },
deselectAll() { unselectAll() {
this.sounds.forEach(sound => { this.sounds.forEach(sound => {
sound.active = false; sound.active = false;
}); });
@@ -100,7 +119,8 @@ let app = {
const sound = random.element(this.sounds.filter(sound => sound.active)); const sound = random.element(this.sounds.filter(sound => sound.active));
if (sound !== null) { if (sound !== null) {
console.log(`Playing ${sound.id}`); console.log(`Playing ${sound.id}`);
sound.audio.playbackRate = random.float(0.8, 1.2); sound.audio.playbackRate = this.randomPitch ? random.float(0.8, 1.2) : 1;
sound.audio.volume = this.volume / 100;
sound.audio.play(); sound.audio.play();
} }
this.trigger(); this.trigger();
Binary file not shown.
BIN
View File
Binary file not shown.
Binary file not shown.
BIN
View File
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
View File
Binary file not shown.
Binary file not shown.
+89 -2
View File
@@ -85,8 +85,8 @@ CUSTOM STYLE
:root { :root {
/* https://materialui.co/colors/ */ /* https://materialui.co/colors/ */
--hue-primary: 65.52; --hue-primary: 348.36;
--sat-primary: 20%; --sat-primary: 100.00%;
--background: hsl(var(--hue-primary), var(--sat-primary), 96.08%); --background: hsl(var(--hue-primary), var(--sat-primary), 96.08%);
--background-primary: hsl(var(--hue-primary), var(--sat-primary), 93.33%); --background-primary: hsl(var(--hue-primary), var(--sat-primary), 93.33%);
--background-secondary: hsl(var(--hue-primary), var(--sat-primary), 90%); --background-secondary: hsl(var(--hue-primary), var(--sat-primary), 90%);
@@ -169,3 +169,90 @@ a {
font-size: inherit; font-size: inherit;
} }
} }
button.red {
--hue-primary: 4.11;
--sat-primary: 89.62%;
}
button.green {
--hue-primary: 87.77;
--sat-primary: 50.21%;
}
button.inactive {
--sat-primary: 25%;
}
button:disabled {
--sat-primary: 5%;
cursor: no-drop;
}
button {
border: 1px solid hsl(var(--hue-primary), var(--sat-primary), 50%);
background-color: hsl(var(--hue-primary), var(--sat-primary), 90%);
border-radius: 0.5em;
cursor: pointer;
padding: .25em .5em;
}
button:hover {
background-color: hsl(var(--hue-primary), var(--sat-primary), 92%);
}
button:active {
background-color: hsl(var(--hue-primary), var(--sat-primary), 94%);
}
.sound-container {
display:flex;
flex-flow: row wrap;
gap: .5em;
align-items: stretch;
justify-content: center;
}
.sound-container button {
flex-basis: 31%;
height: 4em;
width: 100%;
position:relative;
padding: .5em;
}
.sound-container button::before {
content: '🔊';
display: block;
position: absolute;
top: .25em;
left: .25em;
}
.sound-container button.inactive::before {
content: '🔇';
}
h3 > a {
font-weight: normal;
}
.start {
width: 100%;
font-weight: bold;
font-size: larger;
padding: .5em;
}
table.config td:first-child {
text-align: right;
}
table.config input {
width: 100%;
}
table.config button {
width: 100%;
margin-top: .5em;
}