sound proximity

This commit is contained in:
“chrisshank” 2024-09-10 23:04:09 -07:00
parent 7523465c0b
commit 14a519b705
2 changed files with 121 additions and 55 deletions

View File

@ -62,41 +62,6 @@
</video>
</spatial-geometry>
<spatial-geometry x="415" y="295" width="166" height="200">
<video loop>
<source src="/dancing-flower.mov" type="video/quicktime" />
<source src="/dancing-flower.webm" type="video/webm" />
</video>
</spatial-geometry>
<spatial-geometry x="20" y="525" width="166" height="200">
<video loop>
<source src="/dancing-flower.mov" type="video/quicktime" />
<source src="/dancing-flower.webm" type="video/webm" />
</video>
</spatial-geometry>
<spatial-geometry x="160" y="545" width="166" height="200">
<video loop>
<source src="/dancing-flower.mov" type="video/quicktime" />
<source src="/dancing-flower.webm" type="video/webm" />
</video>
</spatial-geometry>
<spatial-geometry x="290" y="530" width="166" height="200">
<video loop>
<source src="/dancing-flower.mov" type="video/quicktime" />
<source src="/dancing-flower.webm" type="video/webm" />
</video>
</spatial-geometry>
<spatial-geometry x="420" y="520" width="166" height="200">
<video loop>
<source src="/dancing-flower.mov" type="video/quicktime" />
<source src="/dancing-flower.webm" type="video/webm" />
</video>
</spatial-geometry>
<script type="module">
import { SpatialGeometry } from '../src/canvas/spatial-geometry.ts';
import { RecordPlayer } from '../src/music/record-player.ts';
@ -104,28 +69,112 @@
SpatialGeometry.register();
RecordPlayer.register();
const flowers = Array.from(document.querySelectorAll('video'));
const audio = document.querySelector('audio');
let proximityDistance = 200;
const proximitySet = new Set();
const recordPlayerGeometry = document.querySelector('spatial-geometry:has(record-player)');
const recordPlayer = recordPlayerGeometry.firstElementChild;
const flowers = document.querySelectorAll('spatial-geometry:has(video)');
// set playback rate when video is ready
function setPlayback(e) {
e.target.playbackRate = (91 / 60) * e.target.duration;
e.target.removeEventListener('canplay', setPlayback);
}
flowers.forEach((el) => {
el.addEventListener('canplay', setPlayback);
el.firstElementChild.addEventListener('canplay', setPlayback);
});
audio.addEventListener('play', () => {
setTimeout(() => flowers.forEach((el) => el.play()), 1200);
function collisionDetection(rect1, rect2) {
return (
rect1.left < rect2.right &&
rect1.right > rect2.left &&
rect1.top < rect2.bottom &&
rect1.bottom > rect2.top
);
}
function proximityDetection(rect1, rect2, distance = 30) {
return collisionDetection(
DOMRectReadOnly.fromRect({
x: rect1.x - distance,
y: rect1.y - distance,
height: rect1.height + distance * 2,
width: rect1.width + distance * 2,
}),
rect2
);
}
function updateFlowerProximity(flower) {
const alreadyIntersection = proximitySet.has(flower);
// TODO: refactor this hack once resizing and the vertices API are figured out
const isNowIntersecting = proximityDetection(
DOMRectReadOnly.fromRect({
x: recordPlayerGeometry.x,
y: recordPlayerGeometry.y,
height: recordPlayerGeometry.height,
width: recordPlayerGeometry.width,
}),
DOMRectReadOnly.fromRect({
x: flower.x,
y: flower.y,
height: flower.height,
width: flower.width,
}),
proximityDistance
);
const video = flower.firstElementChild;
if (isNowIntersecting && !alreadyIntersection) {
proximitySet.add(flower);
if (!recordPlayer.paused) {
video.play();
}
} else if (alreadyIntersection && !isNowIntersecting) {
proximitySet.delete(flower);
video.pause();
}
}
function updateRecordPlayerProximity() {
proximitySet.forEach((el) => el.firstElementChild.pause?.());
proximitySet.clear();
for (const flower of flowers) {
updateFlowerProximity(flower);
}
}
function handleProximity(e) {
if (e.target === recordPlayerGeometry) {
updateRecordPlayerProximity();
} else {
updateFlowerProximity(e.target);
}
}
document.addEventListener('move', handleProximity);
document.addEventListener('resize', handleProximity);
document.addEventListener('playing', (e) => {
proximitySet.forEach((el) => el.firstElementChild.play?.());
});
audio.addEventListener('pause', () => {
flowers.forEach((el) => {
el.pause();
el.currentTime = 0;
document.addEventListener('stopped', (e) => {
proximitySet.forEach((el) => {
el.firstElementChild.pause?.();
el.firstElementChild.currentTime = 0;
});
});
function updateVolume() {
proximityDistance = recordPlayer.volume * 500;
updateRecordPlayerProximity();
}
updateVolume();
document.addEventListener('volume', updateVolume);
</script>
</body>
</html>

View File

@ -74,6 +74,10 @@ styles.replaceSync(`
top: 25px;
right: 95px;
transform-origin: top;
--move-time: 3s;
animation-fill-mode: forwards;
animation-timing-function: linear;
}
.control {
@ -150,20 +154,17 @@ styles.replaceSync(`
}
:host(:state(playing)) .tone-arm {
--move-time: 3s;
animation-fill-mode: forwards;
animation-timing-function: linear;
animation:
position-arm var(--move-time),
ready-arm var(--move-time),
move-arm var(--duration) var(--move-time),
reset-arm var(--move-time) calc(var(--duration) + var(--move-time));
}
}
:host(:state(playing)) .record {
animation-play-state: running;
}
@keyframes position-arm {
@keyframes ready-arm {
20% {
transform: rotateX(20deg);
}
@ -184,14 +185,18 @@ styles.replaceSync(`
}
to {
rotate: 43deg;
rotate: 42deg;
}
}
@keyframes reset-arm {
0% {
rotate: 42deg;
}
20% {
transform: rotateX(20deg);
rotate: 43deg;
rotate: 42deg;
}
80% {
@ -225,6 +230,8 @@ export class RecordPlayer extends HTMLElement {
super();
this.addEventListener('click', this);
this.#audio.volume = 0.5;
this.#audio.addEventListener('ended', this);
const shadow = this.attachShadow({ mode: 'open' });
@ -239,7 +246,7 @@ export class RecordPlayer extends HTMLElement {
<div class="control"></div>
</div>
<button class="btn"></button>
<input type="range" class="slider" min="0" max="1" step="0.05" value="0.75">
<input type="range" class="slider" min="0" max="1" step="0.05" value="0.5">
</div>
<slot></slot>`;
@ -251,6 +258,10 @@ export class RecordPlayer extends HTMLElement {
return this.#audio.paused;
}
get volume() {
return this.#audio.volume;
}
#playTimeout: number = -1;
play() {
@ -259,7 +270,10 @@ export class RecordPlayer extends HTMLElement {
this.#audio.volume = this.#volumeInput.valueAsNumber;
this.style.setProperty('--duration', `${this.#audio.duration}s`);
this.#internals.states.add('playing');
this.#playTimeout = window.setTimeout(() => this.#audio.play(), 3000);
this.#playTimeout = window.setTimeout(() => {
this.#audio.play();
this.dispatchEvent(new Event('playing', { bubbles: true }));
}, 3000);
}
stop() {
@ -269,6 +283,7 @@ export class RecordPlayer extends HTMLElement {
this.#internals.states.delete('playing');
this.#audio.pause();
this.#audio.currentTime = 0;
this.dispatchEvent(new Event('stopped', { bubbles: true }));
}
handleEvent(event: Event) {
@ -283,7 +298,9 @@ export class RecordPlayer extends HTMLElement {
}
case 'input': {
event.stopPropagation();
this.#audio.volume = this.#volumeInput.valueAsNumber;
this.dispatchEvent(new Event('volume', { bubbles: true }));
return;
}