mmelodies/src/controller/controller.ts

324 lines
9.5 KiB
TypeScript

import * as THREE from 'three';
// @ts-ignore
import { UpdateRequest, Update } from "/proto/mmelodies.proto";
// Subscribe to the audioprocess event
let socket: WebSocket | null = null;
function connect_ws() {
const url = new URL("/api", location.href);
url.protocol = "ws";
if (location.protocol.startsWith("https")) {
url.protocol = "wss";
}
const ws = new WebSocket(url);
ws.binaryType = "arraybuffer";
ws.addEventListener("open", () => {
const message = UpdateRequest.create({ paramRefresh: true });
const request = UpdateRequest.encode(message).finish();
if (socket) {
socket.send(request);
}
});
ws.addEventListener("error", (event) => {
console.log(event);
});
ws.addEventListener("close", () => {
socket = null;
setTimeout(() => {
socket = connect_ws();
}, 2500);
});
// Listen for messages
ws.addEventListener("message", (event) => {
const message = Update.decode(new Uint8Array(event.data));
console.log(message);
});
return ws;
}
socket = connect_ws();
// Select all range input elements
const sliders = document.querySelectorAll(
"input[type=range]",
) as NodeListOf<HTMLInputElement>;
// Add an event listener to each slider
sliders.forEach((slider, i) => {
slider.addEventListener("input", function () {
console.log(`Slider ${this.id} position: ${this.value}`);
if (socket) {
const message = UpdateRequest.create({ paramChanges: [{ id: i, value: this.value }] });
const buffer = UpdateRequest.encode(message).finish();
socket.send(buffer);
}
});
});
// Declare a global interface for the window object
declare global {
interface Window {
Accelerometer: any;
Gyroscope: any;
AbsoluteOrientationSensor: any;
}
}
// Show elements hidden by default
function show(element: HTMLElement) {
if (element) {
element.style.display = "flex";
}
}
function checkPerms() {
const permissionsToCheck = ['accelerometer', 'gyroscope', 'magnetometer'];
const permissionPromises = permissionsToCheck.map(permissionName => {
if (!navigator.permissions.query) {
console.log('Permissions API not supported');
return Promise.resolve(null);
}
return navigator.permissions.query({ name: permissionName as any })
.catch(error => {
console.log(`Permission not supported: ${permissionName}`);
return null;
});
}).filter(Boolean);
Promise.all(permissionPromises).then((results: (PermissionStatus | null)[]) => {
const warning = document.getElementById("unsupported-warning") as HTMLElement;
const gyro = document.getElementById("gyroBox") as HTMLElement;
const accel = document.getElementById("accelBox") as HTMLElement;
const orient = document.getElementById("orientBox") as HTMLElement;
let isAccelerometerSupported = "Accelerometer" in window;
let isGyroscopeSupported = "Gyroscope" in window;
let isAbsoluteOrientationSensorSupported = "AbsoluteOrientationSensor" in window;
if (!results.every((result) => result !== null && result.state === "granted")) {
show(warning)
console.log("shown")
} else {
if (isAccelerometerSupported && false) {
console.log("Accelerometer is supported.");
show(accel)
sensorStart("Accelerometer", accel, "accel");
}
if (isGyroscopeSupported && false) {
console.log("Gyroscope is supported.");
show(gyro)
sensorStart("Gyroscope", gyro, "gyro");
}
if (isAccelerometerSupported) {
console.log("AbsoluteOrientationSensor is supported.");
show(orient)
sensorStart("AbsoluteOrientationSensor", orient, "orient");
}
if (!isAccelerometerSupported && !isGyroscopeSupported && !isAbsoluteOrientationSensorSupported) {
console.log("None of the interfaces are supported.");
show(warning)
}
}
})
};
function setupThreeJsScene() {
const sceneSize = new THREE.Vector3(100, 100, 100);
const renderer = new THREE.WebGLRenderer({ alpha: true });
renderer.setSize(sceneSize.x, sceneSize.y);
renderer.setPixelRatio(window.devicePixelRatio / 2.5);
const scene = new THREE.Scene();
const axes = new THREE.AxesHelper(5);
scene.add(axes);
const camera = new THREE.PerspectiveCamera(75, sceneSize.x / sceneSize.y, 0.1, 1000);
camera.translateOnAxis(scene.up.clone(), 2);
camera.lookAt(axes.position);
return { renderer, scene, axes, camera };
}
function handleSensorReading(
sensor: any,
scene: THREE.Scene,
calibratedDirection: THREE.Vector3 | null,
currentDirection: THREE.Vector3 | null,
relativeDirection: THREE.Vector3 | null,
render: (direction: THREE.Quaternion | null) => void,
checkbox: HTMLInputElement | null
) {
let fps = 0;
const setFPS = 60;
sensor.addEventListener('reading', () => {
// Apply the sensor's quaternion to the vector
const orientation = new THREE.Quaternion(...sensor.quaternion).normalize();
// Set the initial orientation if it's not set already
currentDirection = scene.up.clone().applyQuaternion(orientation);
if (!calibratedDirection) {
calibratedDirection = currentDirection;
console.log("cal", calibratedDirection);
}
console.log("debug")
relativeDirection = calibratedDirection.clone().sub(currentDirection).normalize();
const relativeOrientation = new THREE.Quaternion().setFromUnitVectors(calibratedDirection, currentDirection);
render(relativeOrientation);
});
// Check if the button was found
if (checkbox) {
// Add a click event listener to the button
checkbox.addEventListener('change', function () {
// If sensor is already running, stop it
if (!this.checked) {
sensor.stop();
console.log("stopped");
fps = 0;
render(null);
} else {
// Otherwise, start the sensor
sensor.start();
console.log("started");
calibratedDirection = null;
fps = setFPS;
}
});
}
else {
console.log(checkbox)
}
}
type sensorType = "Accelerometer" | "Gyroscope" | "AbsoluteOrientationSensor";
function sensorStart(sensorName: sensorType, box: HTMLElement, checkboxId: string) {
const sensorConstructors: { [K in sensorType]: any } = {
"Accelerometer": window.Accelerometer,
"Gyroscope": window.Gyroscope,
"AbsoluteOrientationSensor": window.AbsoluteOrientationSensor
};
const SensorConstructor = sensorConstructors[sensorName];
if (SensorConstructor) {
const sensor = new SensorConstructor({ frequency: 60, referenceFrame: 'device' });
// Set up Three.js scene
const { renderer, scene, axes, camera } = setupThreeJsScene();
box.appendChild(renderer.domElement)
// Define render function
const render = (direction: THREE.Quaternion | null) => {
if (!direction) {
direction = new THREE.Quaternion().identity();
}
axes.setRotationFromQuaternion(direction);
renderer.render(scene, camera);
}
// Initialize default orientation
let calibratedDirection: THREE.Vector3 | null = null;
let currentDirection: THREE.Vector3 | null = null;
let relativeDirection: THREE.Vector3 | null = null;
var checkbox = document.getElementById(checkboxId) as HTMLInputElement;
// Handle sensor readings
handleSensorReading(sensor, scene, calibratedDirection, currentDirection, relativeDirection, render, checkbox);
render(null);
// Render the first frame
} else {
console.log(`No constructor found for sensor: ${sensorName}`);
}
}
function gyroStart() {
// Create an AbsoluteOrientationSensor object
const sensor = new window.AbsoluteOrientationSensor({ frequency: 60, referenceFrame: 'device' });
// Create a renderer
const sceneSize = new THREE.Vector3(100, 100, 100);
const renderer = new THREE.WebGLRenderer({ alpha: true });
renderer.setSize(sceneSize.x, sceneSize.y);
renderer.setPixelRatio(window.devicePixelRatio / 2.5);
const scene = new THREE.Scene();
const axes = new THREE.AxesHelper(5);
scene.add(axes);
const camera = new THREE.PerspectiveCamera(75, sceneSize.x / sceneSize.y, 0.1, 1000);
camera.translateOnAxis(scene.up.clone(), 2);
camera.lookAt(axes.position);
// Get the checkbox element
const gyroBox = document.getElementById("gyroBox") as HTMLElement;
const checkbox = document.getElementById("gyro") as HTMLInputElement;
(gyroBox || document.body).appendChild(renderer.domElement);
show(gyroBox);
// Create an arrow
// Profiling
let sampleCount = 0; // Number of frames since the last second
let sampleSec = 0; // Time last second was recorded for a frame
// Animation loop
let setFPS = 60; // Desired framerate
let fps = 0; // Startup fps
// Initialize default orientation
let calibratedDirection: THREE.Vector3 | null;
let currentDirection: THREE.Vector3 | null;
let relativeDirection: THREE.Vector3 | null;
const render = (direction: THREE.Quaternion | null) => {
if (!direction) {
direction = new THREE.Quaternion().identity();
}
axes.setRotationFromQuaternion(direction);
renderer.render(scene, camera);
}
// Add an event listener for sensor readings
sensor.addEventListener('reading', () => {
// Apply the sensor's quaternion to the vector
const orientation = new THREE.Quaternion(...sensor.quaternion).normalize();
// Set the initial orientation if it's not set already
currentDirection = scene.up.clone().applyQuaternion(orientation);
if (!calibratedDirection) {
calibratedDirection = currentDirection;
console.log("cal", calibratedDirection);
}
relativeDirection = calibratedDirection.clone().sub(currentDirection).normalize();
const relativeOrientation = new THREE.Quaternion().setFromUnitVectors(calibratedDirection, currentDirection);
render(relativeOrientation);
// Profiling
let currTime = new Date().getTime();
if (sampleSec < currTime - 1000) {
console.log("Samples:", sampleCount)
sampleCount = 0;
sampleSec = currTime;
}
else { sampleCount++; }
});
// Render the first frame
render(null);
}
checkPerms();