375 lines
9.7 KiB
HTML
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> |