heading-ears/archive/sound_finder_v1.html
2024-10-12 15:28:34 +09:00

375 lines
9.7 KiB
HTML

<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta charset="utf-8">
<script src="js/p5.min.js"></script>
<script src="js/Tone-14.8.36.min.js"></script>
<style>
html,
body {
margin: 0;
padding: 0;
}
canvas {
display: block;
}
.overlay-userinput {
display: grid;
justify-content: center;
align-items: center;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(255, 255, 255, 0.5);
color: white;
z-index: 2;
/* display: none; */
}
</style>
</head>
<body>
<button class='overlay-userinput' onclick='requestPermissions();'>
<div>터치하고-시작하기!</div>
</button>
<script>
//
//shared
//device location & motion
let heading = 0;
let latitude = 37;
let longitude = 126;
// let latitude = 0;
// let longitude = 0;
//audio playback permission checker
let silence;
let clap;
//all ready flag (not used)
let ready = false;
//promisify -> new Tone.Player
function AudioImport(url) {
return new Promise((resolve, reject) => {
var audio = new Tone.Player(url, () => resolve(audio));
});
}
//clear all permissions
function requestPermissions() {
// device orientation data permission
if (navigator.userAgent.match(/(iPod|iPhone|iPad)/)) {
requestOrientationPermission();
}
// sound playback permission
silence.start();
//
let veil = document.querySelector(".overlay-userinput");
veil.style.display = 'none';
//
ready = true;
}
//gps, heading
//get permissions
if (navigator.userAgent.match(/(iPod|iPhone|iPad)/)) {
function requestOrientationPermission() {
DeviceOrientationEvent
.requestPermission()
.then(response => {
if (response == 'granted') {
window.addEventListener("deviceorientation", (event) => {
if (event.webkitCompassHeading) {
heading = event.webkitCompassHeading * -1;
}
}, true);
}
})
.catch(console.error)
}
} else {
window.addEventListener("deviceorientationabsolute", (event) => {
heading = event.alpha;
}, true);
}
const watchID = navigator.geolocation.watchPosition(
(position) => {
latitude = position.coords.latitude;
longitude = position.coords.longitude;
},
(error) => {}, {
enableHighAccuracy: true,
maximumAge: 30000,
timeout: 27000,
}
);
//distance between 2 locations (lat/lon)
function getDistance(lat1, lon1, lat2, lon2) {
var R = 6371; // Radius of the earth in km
var dLat = deg2rad(lat2 - lat1); // deg2rad below
var dLon = deg2rad(lon2 - lon1);
var a = Math.sin(dLat / 2) * Math.sin(dLat / 2) +
Math.cos(deg2rad(lat1)) * Math.cos(deg2rad(lat2)) * Math.sin(dLon / 2) * Math.sin(dLon / 2);
var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
var d = R * c; // Distance in km
return d;
}
function deg2rad(deg) {
return deg * (Math.PI / 180)
}
//bearing between 2 locations (lat/lon)
// Converts from degrees to radians.
function toRadians(degrees) {
return degrees * Math.PI / 180;
};
// Converts from radians to degrees.
function toDegrees(radians) {
return radians * 180 / Math.PI;
}
// const y = Math.sin(λ2-λ1) * Math.cos(φ2);
// const x = Math.cos(φ1)*Math.sin(φ2) -
// Math.sin(φ1)*Math.cos(φ2)*Math.cos(λ2-λ1);
// const θ = Math.atan2(y, x);
// const brng = (θ*180/Math.PI + 360) % 360; // in degrees
function getBearing(startLat, startLng, destLat, destLng) {
startLat = toRadians(startLat);
startLng = toRadians(startLng);
destLat = toRadians(destLat);
destLng = toRadians(destLng);
y = Math.sin(destLng - startLng) * Math.cos(destLat);
x = Math.cos(startLat) * Math.sin(destLat) -
Math.sin(startLat) * Math.cos(destLat) * Math.cos(destLng - startLng);
brng = Math.atan2(y, x);
brng = toDegrees(brng);
return (brng + 360) % 360;
}
// sounds
class Sounder {
constructor(soundfile, latitude, longitude, spread) {
this.soundfile = soundfile;
this.latitude = latitude;
this.longitude = longitude;
this.spread = spread; //deg
}
async load() {
//start sound playback
this.pv = new Tone.PanVol(0, -99).toDestination();
this.snd = await AudioImport(this.soundfile); // NOTE: url with spaces didn't work here.
this.snd.connect(this.pv).start();
this.snd.loop = true;
}
update() {
//
//update sound pan/volume
//
this.ang = getBearing(latitude, longitude, this.latitude, this.longitude); //deg
this.angerr = (((heading - this.ang + 360) % 360) + 180) % 360 - 180;
this.dist = getDistance(latitude, longitude, this.latitude, this.longitude); //km
//
this.distvol = map(this.dist, 0.005, 0.05, 0, -10, true); //(dB)
// (from Pure Data patch "iamyou", [eqpan2~])
// arg #1 (inlet #3): width:
// -width*(1.5) ~ -width/2 -> left fade-in
// -width/2 ~ width/2 -> cross fading
// +width/2 ~ width*(1.5) -> right fade-out
let panleft_start = this.spread * (-1.5);
let panleft_end = this.spread * (-0.5);
let panright_start = this.spread * (0.5);
let panright_end = this.spread * (1.5);
//left fade-in
if (this.angerr > panleft_start && this.angerr < panleft_end) {
this.pv.pan.value = -1; //left-full
this.pv.volume.exponentialRampTo(this.distvol + map(this.angerr, panleft_start, panleft_end, -50, 0), 3); //(dB)
}
//cross fading
else if (this.angerr > panleft_end && this.angerr < panright_start) {
this.pv.pan.value = map(this.angerr, panleft_end, panright_start, -1, 1); // crossfade
this.pv.volume.exponentialRampTo(this.distvol, 3); //(dB)
}
//right fade-out
else if (this.angerr > panright_start && this.angerr < panright_end) {
this.pv.pan.value = 1; //right-full
this.pv.volume.exponentialRampTo(this.distvol + map(this.angerr, panright_start, panright_end, 0, -50), 3); //(dB)
}
//slience
else {
this.pv.pan.value = 0; //silent
this.pv.volume.exponentialRampTo(-99, 10); //(dB)
}
}
draw() {
//draw sound location
}
}
//p5
class Compass {
constructor(size, colors) {
this.size = size;
this.colors = colors;
this.heading = 0;
}
draw() {
push();
//
scale(this.size);
//
translate(0.5, 0.5);
//
noStroke();
//
fill(this.colors[0]);
circle(0, 0, 0.9);
//
fill(this.colors[1]);
rotate(this.heading);
quad(0.1, 0.2, 0, -0.4, -0.1, 0.2, 0, 0.1);
//
fill(this.colors[2]);
circle(0, 0, 0.1);
//
pop();
}
}
let compass_target;
let compass_north;
let sounds = [];
async function preload() {
//some sounds for check-in
silence = (await AudioImport("./audio/_silence.wav")).toDestination();
clap = (await AudioImport("./audio/clap01.mp3")).toDestination();
//register sounders to the list
sounds.push(new Sounder("./audio/delayecho.mp3", 37.57451, 126.92612, 30));
//preload all sounds
sounds.forEach(async item => await item.load());
}
function setup() {
createCanvas(windowWidth, windowHeight);
compass_target = new Compass(windowWidth - 100, ['springgreen', 'navy', 'white']);
compass_north = new Compass(40, ['yellow', 'navy', 'white']);
angleMode(DEGREES);
}
let lines = 0;
let linestep = 20;
function textline(t, restart) {
if (restart) lines = 0;
else lines = lines + 1;
text(t, 0, linestep * lines);
}
function draw() {
//
background('olive');
//all data is ready to be used.
if (ready) {
//update sounders
sounds.forEach(async item => await item.update());
//
let target = sounds[0];
////main group
//(push)
push();
//compass (the North)
translate((windowWidth - compass_target.size) / 2, (windowWidth - compass_target.size) / 2, 0);
compass_target.heading = target.angerr; //target #0
compass_target.draw();
//debug
fill('white');
translate(-30, 0, 0);
textline('angerr', true);
textline(target.angerr);
textline('distance');
textline(target.dist);
textline('pan');
textline(target.pv.pan.value);
textline('distvol');
textline(target.distvol);
textline('vol');
textline(target.pv.volume.value);
textline('target_lat');
textline(target.latitude);
textline('target_lon');
textline(target.longitude);
//(gap)
translate(0, compass_target.size, 0);
//gps
translate(0, 20, 0);
fill('white');
textline('current location', true);
textline(latitude);
textline(longitude);
//
//(pop)
pop();
////status group
//(push)
push();
//
translate(windowWidth - 50, windowHeight - 50, 0);
compass_north.heading = heading;
compass_north.draw();
//(pop)
pop();
}
}
</script>
</body>
</html>