projection test
This commit is contained in:
parent
cc44e24e60
commit
206d768fcb
3 changed files with 844 additions and 2 deletions
|
|
@ -46,7 +46,7 @@ body::-webkit-scrollbar {
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
width: 100vw;
|
width: 100vw;
|
||||||
/* z-index usage is up to you.. although there is no need of using it because the default stack context will work. */
|
/* z-index usage is up to you.. although there is no need of using it because the default stack context will work. */
|
||||||
z-index: -1; // this is optional
|
z-index: -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.arrow {
|
.arrow {
|
||||||
|
|
|
||||||
29
server.js
29
server.js
|
|
@ -80,11 +80,38 @@ fastify.get("/", async function (request, reply) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
//get '/projection'
|
||||||
|
fastify.get("/projection", async function (request, reply) {
|
||||||
|
//get list
|
||||||
|
let list = await fs.readdir(process.env.userdata);
|
||||||
|
list.reverse();
|
||||||
|
|
||||||
|
// console.log(list);
|
||||||
|
|
||||||
|
let folders = [];
|
||||||
|
for (const item of list) {
|
||||||
|
let json = await fs.readFile(process.env.userdata + item + '/fields.json')
|
||||||
|
.catch((err) => {
|
||||||
|
console.error(err);
|
||||||
|
});
|
||||||
|
if (json != undefined) {
|
||||||
|
var fields = JSON.parse(json.toString('utf8'));
|
||||||
|
folders.push({
|
||||||
|
foldername: item,
|
||||||
|
group: fields.group,
|
||||||
|
title: fields.title,
|
||||||
|
comment: fields.comment,
|
||||||
|
anchor: fields.anchor
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// console.log(folders);
|
// console.log(folders);
|
||||||
|
|
||||||
//
|
//
|
||||||
reply.view("/src/pages/parade.html", {
|
reply.view("/src/pages/projection.html", {
|
||||||
list: folders
|
list: folders
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
815
src/pages/projection.html
Normal file
815
src/pages/projection.html
Normal file
|
|
@ -0,0 +1,815 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<title>흐름을 향하여 걷는 Walking towards the Flow</title>
|
||||||
|
<meta itemprop="description" content="온라인 공간에 소리의 행렬을 만드는 사운드 퍼레이드입니다.">
|
||||||
|
<meta itemprop="image" content="/thumbnail.jpg">
|
||||||
|
<link rel="stylesheet" href="/default.css" />
|
||||||
|
<style>
|
||||||
|
html,
|
||||||
|
body {
|
||||||
|
overflow-x: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bg {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
background: unset;
|
||||||
|
background-color: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
#defaultCanvas0 {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.clear {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 500px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#p5 {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tools {
|
||||||
|
padding: 1em 1em;
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align: top;
|
||||||
|
}
|
||||||
|
|
||||||
|
.penselect {
|
||||||
|
margin: 1em 0em;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<script src="/js/p5-v1.1.9.min.js"></script>
|
||||||
|
<script src="/js/socket-v2.3.0.io.slim.js"></script>
|
||||||
|
<script src="/js/Tone-14.8.36.min.js"></script>
|
||||||
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+KR:wght@500&display=swap" rel="stylesheet" />
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div class="bg"></div>
|
||||||
|
<div id="top"></div>
|
||||||
|
<div class="parade"></div>
|
||||||
|
<div class="entry">
|
||||||
|
<div class="notice">
|
||||||
|
<div class="sticky">
|
||||||
|
<div class="first"><a href="/en/">EN</a></div>
|
||||||
|
<div class="second"><a href="#top">퍼레이드</a></div>
|
||||||
|
</div>
|
||||||
|
<section>
|
||||||
|
<h1>소개</h1>
|
||||||
|
<p>
|
||||||
|
«흐름을 향하여 걷는»은 온라인 공간에서 열리는 사운드 퍼레이드입니다. 이 소리의 행렬은 여러분이 보내주시는 소리가 모여 만들어지며, 누구나 참여하실 수 있습니다.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
소리가 흐르는 길은 별들이 순회하는 저 은하수와 같이, 언제나 그 자리에 있으면서 여러분이 소리를 듣고 있든 그렇지 않든 흐르고 있습니다.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
이 영원한 퍼레이드의 영속성은 우리에게 최후의 안도를 줍니다. 때로는 자신이 위성궤도를 무한히 돌고 있는 우주 부스러기라는 생각이 들다가도, 그것이 관측되는 순간 더는 외로움이나 잊혀짐이 아니라
|
||||||
|
기억됨이 됩니다.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
«흐름을 향하여 걷는»은 온사이트 공연으로 함께 하실 수도 있습니다. 11월 27일(일) 진행하는 공연에서는 웹사이트에 모인 소리들이 물리적 공간에 펼쳐지고, 관객은 함께 걷고 멈추며 흐름에
|
||||||
|
공감해주신 분들의 소리를 듣습니다.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
온라인으로 진행하는 소리 모집과 온사이트 공연에 많은 관심과 참여 바랍니다.<br />
|
||||||
|
</p>
|
||||||
|
<br>
|
||||||
|
<div class="info">
|
||||||
|
<p>
|
||||||
|
소리 모집<br />
|
||||||
|
2022년 11월 13일(일)–26일(토)
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
공연 일정<br />
|
||||||
|
2022년 11월 27일(일), 16:00
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
공연 장소<br />
|
||||||
|
서울시립미술관 서소문본관 1층 로비, 2층 러닝스테이션, 옥상정원
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
공연 구성<br />
|
||||||
|
무리0 약속들 — 무리1 깃발들 — 무리2 신체들 — 무리3 사물들 — 무리4 누구들
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
공연 신청<br />
|
||||||
|
<a href="https://zrr.kr/aHTS" target="_blank">https://zrr.kr/aHTS</a>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
작가<br />
|
||||||
|
<a href="http://dianaband.info" target="_blank">다이애나밴드</a> × <a href="https://cgyoon.kr/"
|
||||||
|
target="_blank">윤충근</a>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
문의<br />wonjung24@gmail.com
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<section class="participation">
|
||||||
|
<h1>참여 방법</h1>
|
||||||
|
<ol>
|
||||||
|
<li>1. 무리의 특성에 따라 녹음기기나 핸드폰으로 30초 가량의 소리를 녹음해주세요.
|
||||||
|
<ol class="category">
|
||||||
|
<li>• 무리1 깃발들 — 좋아하는 것, 가치에 대해 다섯 번 외쳐주세요.</li>
|
||||||
|
<li>• 무리2 신체들 — 몸에서 나는 소리를 녹음해 보아요. 박수, 휘파람도 좋아요.</li>
|
||||||
|
<li>• 무리3 사물들 — 주변 사물들의 소리를 찾아 주세요. 뽁뽁이 소리, 구슬 소리</li>
|
||||||
|
<li>• 무리4 누구들 — 누구의 소리를 모아주세요. 반려동물, 물 소리, 산책의 장소</li>
|
||||||
|
</ol>
|
||||||
|
</li>
|
||||||
|
<li>2. 녹음한 소리 파일을 업로드해주세요.</li>
|
||||||
|
<li>3. 소리의 제목과 묘사을 입력한 뒤, 소리의 모양을 그려 제출해주세요.</li>
|
||||||
|
<li>* 혐오 표현이 포함된 음원을 업로드하시면 삭제됩니다.</li>
|
||||||
|
</ol>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<h1>소리 제출</h1>
|
||||||
|
<form id="form" method="POST" enctype="multipart/form-data">
|
||||||
|
<ul class="submit">
|
||||||
|
<li>
|
||||||
|
<h3>1. 소리 유형</h3>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<input type="radio" name="group" autocomplete="off" value="flag" /> 무리1 깃발들
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<input type="radio" name="group" autocomplete="off" value="body" /> 무리2 신체들
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<input type="radio" name="group" autocomplete="off" value="object" /> 무리3 사물들
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<input type="radio" name="group" autocomplete="off" value="any" required /> 무리4 누구들
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<h3>2. 소리 파일</h3>
|
||||||
|
<input type="file" name="audiofile" autocomplete="off" required />
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<h3>3. 소리 제목</h3>
|
||||||
|
<input id="title" name="title" type="text" autocomplete="off" required />
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<h3>4. 소리 묘사</h3>
|
||||||
|
<input id="comment" name="comment" type="text" autocomplete="off" required />
|
||||||
|
</li>
|
||||||
|
<!-- pixels.png ~ made with p5.js -->
|
||||||
|
<li class="noscroll">
|
||||||
|
<h3>5. 소리 모양</h3>
|
||||||
|
<div>
|
||||||
|
<div id="p5">
|
||||||
|
</div>
|
||||||
|
<div class="tools">
|
||||||
|
<div>
|
||||||
|
<input class="penselect" type="radio" value="pencil" name="penselect" id="penselect-pencil"
|
||||||
|
autocomplete="off" checked>
|
||||||
|
<label for="penselect-pencil">연필</label>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<input class="penselect" type="radio" value="erasor" name="penselect" id="penselect-erasor"
|
||||||
|
autocomplete="off">
|
||||||
|
<label for="penselect-erasor">지우개</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button type="button" class="clear">다시그리기!</button>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<h3>6. 비밀 번호</h3>
|
||||||
|
<input id="pass" name="pass" type="text" maxlength="2" pattern="^\d{2}$" title="암호는 숫자x2개로 해주세요."
|
||||||
|
required />
|
||||||
|
<small>두 자리 숫자를 입력해주세요. 업로드한 소리를 삭제할 때 쓰입니다.</small>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<input id="submit" type="submit" value="보내기!" />
|
||||||
|
<progress id="progress" max="100" value="70" style="display:none"></progress>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<p>
|
||||||
|
↓
|
||||||
|
</p>
|
||||||
|
</form>
|
||||||
|
</section>
|
||||||
|
<section id="list">
|
||||||
|
<section id="list_init_kr" style="display:none">
|
||||||
|
<h1>소리 목록</h1>
|
||||||
|
<p>제출해주신 소리는 아래 목록에 시간순으로 쌓입니다. 각각의 모양을 클릭하면, 모양에 해당하는 소리를 듣고 소리에 대한 정보를 확인할 수 있습니다.</p>
|
||||||
|
</section>
|
||||||
|
<section id="list_init_en" style="display:none">
|
||||||
|
<h1>Sound List</h1>
|
||||||
|
<p>The sounds you submitted will be stacked in chronological order in the list below. By clicking on each shape,
|
||||||
|
you can hear the sound corresponding to the shape and check the information about the sound.</p>
|
||||||
|
</section>
|
||||||
|
{{#each list}}
|
||||||
|
<div class="items" foldername="{{this.foldername}}">
|
||||||
|
<details>
|
||||||
|
<summary><a class="anchor" id="{{this.anchor}}"></a><img class="drawing" src="/uploads/{{this.foldername}}/pixels.png" /></summary>
|
||||||
|
<audio class="sound" preload="none" controls>
|
||||||
|
<source src="/uploads/{{this.foldername}}/audio.mp3" type="audio/mpeg">
|
||||||
|
</audio>
|
||||||
|
<ul class="soundinfo"><hr>
|
||||||
|
<li>유형 | <div class="group">{{this.group}}</div></li><hr>
|
||||||
|
<li>제목 | <div class="title">{{this.title}}</div></li><hr>
|
||||||
|
<li>묘사 | <div class="comment">{{this.comment}}</div></li><hr>
|
||||||
|
</ul>
|
||||||
|
<button class="preview" type="button" onclick="javascript:window.open('/preview/{{this.foldername}}', '_blank');">미리보기</button>
|
||||||
|
<button class="delete" type="button" onclick="del(this)">삭제</button>
|
||||||
|
</details>
|
||||||
|
</div>
|
||||||
|
{{/each}}
|
||||||
|
</section>
|
||||||
|
</div><!-- div class="notice" -->
|
||||||
|
</div><!-- div class="entry" -->
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// // force https
|
||||||
|
// var http_confirm = location.href.split(":")[0];
|
||||||
|
// if (http_confirm == "http") {
|
||||||
|
// window.location.replace("https://" + location.host);
|
||||||
|
// }
|
||||||
|
|
||||||
|
function list_reload() {
|
||||||
|
loadJSON('/list', list => {
|
||||||
|
//if lang == kr
|
||||||
|
let list_init = select('#list_init_kr').html();
|
||||||
|
//if lang == en
|
||||||
|
// let list_init = select('#list_init_en').html();
|
||||||
|
|
||||||
|
//
|
||||||
|
var t = "";
|
||||||
|
list.forEach(item => {
|
||||||
|
t = t + `
|
||||||
|
<div class="items" foldername="${item.foldername}">
|
||||||
|
<details>
|
||||||
|
<summary><a class="anchor" id="${item.anchor}"></a><img class="drawing" src="/uploads/${item.foldername}/pixels.png" /></summary>
|
||||||
|
<audio class="sound" preload="none" controls>
|
||||||
|
<source src="/uploads/${item.foldername}/audio.mp3" type="audio/mpeg">
|
||||||
|
</audio>
|
||||||
|
<ul class="soundinfo"><hr>
|
||||||
|
<li>유형 | <div class="group">${item.group}</div></li><hr>
|
||||||
|
<li>제목 | <div class="title">${item.title}</div></li><hr>
|
||||||
|
<li>묘사 | <div class="comment">${item.comment}</div></li><hr>
|
||||||
|
</ul>
|
||||||
|
<button class="preview" type="button" onclick="javascript:window.open('/preview/${item.foldername}', '_blank');">미리보기</button>
|
||||||
|
<button class="delete" type="button" onclick="del(this)">삭제</button>
|
||||||
|
</details>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
});
|
||||||
|
select('#list').html(list_init + t);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
var socket = io(location.host);
|
||||||
|
var n = 0;
|
||||||
|
var fr = 20;
|
||||||
|
var arr = [];
|
||||||
|
var looper;
|
||||||
|
var score;
|
||||||
|
var logo;
|
||||||
|
var silence;
|
||||||
|
var clap;
|
||||||
|
|
||||||
|
//promisify -> new Tone.Player
|
||||||
|
function AudioImport(url) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
var audio = new Tone.Player(url, () => resolve(audio));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
//**ENTRY begin >>>
|
||||||
|
// -- https://stackoverflow.com/a/39577640
|
||||||
|
function dataURLtoBlob(dataURL) {
|
||||||
|
let array, binary, i, len;
|
||||||
|
binary = atob(dataURL.split(",")[1]);
|
||||||
|
array = [];
|
||||||
|
i = 0;
|
||||||
|
len = binary.length;
|
||||||
|
while (i < len) {
|
||||||
|
array.push(binary.charCodeAt(i));
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
return new Blob([new Uint8Array(array)], {
|
||||||
|
type: "image/png",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function submitForm(event) {
|
||||||
|
|
||||||
|
//TODO : first check if there is a drawing or not. (pixels)
|
||||||
|
|
||||||
|
//
|
||||||
|
var submit = document.getElementById("submit");
|
||||||
|
submit.setAttribute('disabled', 'disabled');
|
||||||
|
select("html").style('cursor', 'progress');
|
||||||
|
|
||||||
|
//
|
||||||
|
var form = document.getElementById("form");
|
||||||
|
var fd = new FormData(form);
|
||||||
|
|
||||||
|
//
|
||||||
|
var unit = 45; //mask&override 'unit' -> for crispy png.
|
||||||
|
var img2 = createGraphics(cols * unit + 2, rows * unit + 2);
|
||||||
|
img2.clear();
|
||||||
|
img2.strokeWeight(0);
|
||||||
|
img2.fill(255);
|
||||||
|
for (var c = 0; c < cols; c++) {
|
||||||
|
for (var r = 0; r < rows; r++) {
|
||||||
|
if (p[c * cols + r] == 1) {
|
||||||
|
img2.rect(c * unit + 1, r * unit + 1, unit, unit);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var dataurl = img2.elt.toDataURL();
|
||||||
|
var pixels = dataURLtoBlob(dataurl);
|
||||||
|
fd.append("pixels", pixels, "pixels.png");
|
||||||
|
|
||||||
|
for (var pair of fd.entries()) {
|
||||||
|
console.log(pair[0] + ', ' + pair[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
var request = new XMLHttpRequest();
|
||||||
|
//
|
||||||
|
var progress = select("#progress");
|
||||||
|
progress.style('display', 'unset');
|
||||||
|
//
|
||||||
|
request.open("POST", "/");
|
||||||
|
request.upload.onprogress = (pe) => {
|
||||||
|
if (pe.lengthComputable) {
|
||||||
|
progress.elt.max = pe.total;
|
||||||
|
progress.elt.value = pe.loaded;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
request.onload = function () {
|
||||||
|
console.log('received http response');
|
||||||
|
console.log('response status: ', request.status);
|
||||||
|
console.log('response headers: ', request.getAllResponseHeaders());
|
||||||
|
console.log('response body: ', request.response);
|
||||||
|
}
|
||||||
|
request.onloadend = () => {
|
||||||
|
// check server's final feedback here.
|
||||||
|
if (request.status == 200) {
|
||||||
|
alert('감사합니다. 아래에 소리목록에서 업로드된 음원을 확인해보세요.');
|
||||||
|
} else {
|
||||||
|
alert('앗! 소리 파일 전송에 문제가 생겼어요\n wonjung24@gmail.com에 소리 파일을 한번 더 보내주시면 추가하겠습니다.\n 관리자 정보: 서버 응답 [' + request.status + ']');
|
||||||
|
}
|
||||||
|
// re-enable next upload.
|
||||||
|
submit.removeAttribute('disabled');
|
||||||
|
select("html").style('cursor', 'auto');
|
||||||
|
form.reset();
|
||||||
|
for (let a = 0; a < p.length; a++) p[a] = 0; //clear pixel-drawing, too.
|
||||||
|
// reload list.
|
||||||
|
list_reload();
|
||||||
|
}
|
||||||
|
request.send(fd);
|
||||||
|
|
||||||
|
//
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
|
||||||
|
const form = document.getElementById('form');
|
||||||
|
form.addEventListener('submit', submitForm);
|
||||||
|
|
||||||
|
//// ---- for 'list' rendering ----
|
||||||
|
|
||||||
|
function del(that) {
|
||||||
|
console.log();
|
||||||
|
let text;
|
||||||
|
let pass = prompt("패스워드를 맞춰보세요!", "숫자2개");
|
||||||
|
if (/^\d{2}$/.test(pass)) {
|
||||||
|
var target = that.parentElement.parentElement.getAttribute('foldername');
|
||||||
|
const trydelete = async () => {
|
||||||
|
const response = await fetch('/delete/' + target + '/' + pass);
|
||||||
|
if (response.ok) {
|
||||||
|
const json = await response.json();
|
||||||
|
return Promise.resolve(json);
|
||||||
|
} else {
|
||||||
|
return Promise.reject('no response.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
trydelete().then((resp) => {
|
||||||
|
if (resp.result) {
|
||||||
|
alert("지우기 성공!");
|
||||||
|
// location.reload();
|
||||||
|
list_reload();
|
||||||
|
} else {
|
||||||
|
alert("지우기 실패-");
|
||||||
|
}
|
||||||
|
}).catch(console.log);
|
||||||
|
} else {
|
||||||
|
alert("암호는 숫자2개...");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var p = [];
|
||||||
|
var cols = 17;
|
||||||
|
var rows = 17;
|
||||||
|
var unit = 15; //px
|
||||||
|
var img;
|
||||||
|
var penselect = "pencil";
|
||||||
|
var scrollable = true;
|
||||||
|
// <<< end of **ENTRY
|
||||||
|
|
||||||
|
async function setup() {
|
||||||
|
|
||||||
|
//**PARADE
|
||||||
|
noCanvas();
|
||||||
|
if (windowWidth > 1500 && windowWidth > windowHeight) {
|
||||||
|
fr = 120;
|
||||||
|
} else {
|
||||||
|
fr = 20;
|
||||||
|
}
|
||||||
|
frameRate(fr);
|
||||||
|
|
||||||
|
//
|
||||||
|
|
||||||
|
|
||||||
|
//p5 'draw()' doesn't work if user is not looking at the tab.
|
||||||
|
//noLoop(); // <-- BUT, we want 1 for ENTRY graphics!
|
||||||
|
// --> use custom looper.
|
||||||
|
|
||||||
|
//**ENTRY
|
||||||
|
// list_reload();
|
||||||
|
|
||||||
|
// for korean page...
|
||||||
|
var list_intro = select("#list_init_kr");
|
||||||
|
list_intro.style('display', 'unset');
|
||||||
|
|
||||||
|
var cnv = createCanvas(cols * unit + 2, rows * unit + 2);
|
||||||
|
cnv.parent("p5");
|
||||||
|
img = createGraphics(cols * unit + 2, rows * unit + 2);
|
||||||
|
document.querySelectorAll('.penselect').forEach(item => item.onclick = () => {
|
||||||
|
penselect = document.querySelector('input[name="penselect"]:checked').value;
|
||||||
|
});
|
||||||
|
//
|
||||||
|
for (var i = 0; i < cols * rows; i++) p.push(0);
|
||||||
|
document.querySelectorAll('.clear').forEach(item => item.onclick = () => {
|
||||||
|
for (let a = 0; a < p.length; a++) p[a] = 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
//go to the anchor
|
||||||
|
console.log(window.location.hash.substring(1));
|
||||||
|
let elmnt = document.getElementById(window.location.hash.substring(1));
|
||||||
|
if (elmnt) {
|
||||||
|
elmnt.scrollIntoView(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//**ENTRY begin >>>
|
||||||
|
function draw() {
|
||||||
|
//clear
|
||||||
|
clear();
|
||||||
|
|
||||||
|
//draw the grid
|
||||||
|
stroke(255);
|
||||||
|
strokeWeight(0.2);
|
||||||
|
for (var c = 0; c < cols; c++) {
|
||||||
|
for (var r = 0; r < rows; r++) {
|
||||||
|
noFill();
|
||||||
|
rect(c * unit + 1, r * unit + 1, unit, unit);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//pointer
|
||||||
|
fill(0, 255, 0);
|
||||||
|
strokeWeight(0);
|
||||||
|
|
||||||
|
//mouse way
|
||||||
|
circle(mouseX, mouseY, 10);
|
||||||
|
if (mouseIsPressed && mouseButton === LEFT) {
|
||||||
|
//find slot under the pointer
|
||||||
|
var mouseC = int(mouseX / unit);
|
||||||
|
var mouseR = int(mouseY / unit);
|
||||||
|
if (mouseC >= 0 && mouseC < cols && mouseR >= 0 && mouseR < rows) {
|
||||||
|
if (penselect == "pencil") p[mouseC * cols + mouseR] = 1;
|
||||||
|
else if (penselect == "erasor") p[mouseC * cols + mouseR] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//touch way
|
||||||
|
if (touches.length > 0) {
|
||||||
|
circle(touches[0].x, touches[0].y, 10);
|
||||||
|
//find slot under the pointer
|
||||||
|
var mouseC = int(touches[0].x / unit);
|
||||||
|
var mouseR = int(touches[0].y / unit);
|
||||||
|
if (mouseC >= 0 && mouseC < cols && mouseR >= 0 && mouseR < rows) {
|
||||||
|
// if (scrollable == true) {
|
||||||
|
// scrollable = false;
|
||||||
|
// // firefox browser @ my android phone -> url bar auto-hiding kills drawing experience.
|
||||||
|
// // --> https://stackoverflow.com/a/63221105
|
||||||
|
// document.body.style.marginTop = `-${window.pageYOffset}px`;
|
||||||
|
// document.body.style.position = 'fixed';
|
||||||
|
// document.body.style.overflowY = 'scroll';
|
||||||
|
// }
|
||||||
|
if (penselect == "pencil") p[mouseC * cols + mouseR] = 1;
|
||||||
|
else if (penselect == "erasor") p[mouseC * cols + mouseR] = 0;
|
||||||
|
} else {
|
||||||
|
// if (scrollable == false) {
|
||||||
|
// scrollable = true;
|
||||||
|
// // firefox browser @ my android phone -> url bar auto-hiding kills drawing experience.
|
||||||
|
// // --> https://stackoverflow.com/a/63221105
|
||||||
|
// document.body.style.position = '';
|
||||||
|
// document.body.style.overflowY = '';
|
||||||
|
// if (document.body.style.marginTop) {
|
||||||
|
// const scrollTop = -parseInt(document.body.style.marginTop, 10);
|
||||||
|
// document.body.style.marginTop = '';
|
||||||
|
// window.scrollTo(window.pageXOffset, scrollTop);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//draw img
|
||||||
|
image(img, 0, 0);
|
||||||
|
img.clear();
|
||||||
|
img.strokeWeight(0);
|
||||||
|
img.fill(255);
|
||||||
|
for (var c = 0; c < cols; c++) {
|
||||||
|
for (var r = 0; r < rows; r++) {
|
||||||
|
if (p[c * cols + r] == 1) {
|
||||||
|
img.rect(c * unit + 1, r * unit + 1, unit, unit);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// <<< end of **ENTRY
|
||||||
|
|
||||||
|
//
|
||||||
|
var myroom = -1;
|
||||||
|
var intro;
|
||||||
|
var ready;
|
||||||
|
//
|
||||||
|
socket.on("connect", async function () {
|
||||||
|
console.log("connected!");
|
||||||
|
setTimeout(gotConnection, 1000);
|
||||||
|
console.log("- injecting small delay to make sure p5js loaded. ~ 1s");
|
||||||
|
});
|
||||||
|
|
||||||
|
async function gotConnection() {
|
||||||
|
//
|
||||||
|
console.log("- after delay!");
|
||||||
|
//
|
||||||
|
silence = (await AudioImport("./audio/_silence.wav")).toDestination();
|
||||||
|
clap = (await AudioImport("./audio/clap01.mp3")).toDestination();
|
||||||
|
|
||||||
|
//TESTING... fixed to room 1.
|
||||||
|
// myroom = 1;
|
||||||
|
|
||||||
|
if (myroom == -1 && selectAll(".roomsel").length == 0) {
|
||||||
|
//initial connection -> ask the room number.
|
||||||
|
|
||||||
|
var roomsel = createDiv();
|
||||||
|
selectAll(".parade")[0].child(roomsel);
|
||||||
|
roomsel.class("roomsel");
|
||||||
|
///
|
||||||
|
var b = createButton("시작하기 Start!"); //, "1");
|
||||||
|
var d = createDiv("흐름을 향하여 걷는 Walking towards the Flow");
|
||||||
|
d.class("title");
|
||||||
|
let starter = function () {
|
||||||
|
silence.start();
|
||||||
|
clap.start();
|
||||||
|
// myroom = parseInt(this.value());
|
||||||
|
myroom = 1;
|
||||||
|
|
||||||
|
socket.emit("room", myroom, function (res) {
|
||||||
|
if (res) {
|
||||||
|
console.log("entered the room -> " + myroom);
|
||||||
|
|
||||||
|
// myroom-indicator
|
||||||
|
// var tag = createP(str(myroom));
|
||||||
|
// selectAll(".parade")[0].child(tag);
|
||||||
|
|
||||||
|
//
|
||||||
|
// setTimeout(function() {
|
||||||
|
// ready = createP("퍼레이드 시작합니다!!");
|
||||||
|
// ready.position(
|
||||||
|
// windowWidth / 2 - windowWidth / 10,
|
||||||
|
// windowHeight / 2
|
||||||
|
// );
|
||||||
|
// }, 1000);
|
||||||
|
} else {
|
||||||
|
console.log("rejected!");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
selectAll(".roomsel").forEach(item => {
|
||||||
|
item.remove();
|
||||||
|
// 1 second popup '.intro' div
|
||||||
|
intro = createDiv(
|
||||||
|
"«흐름을 향하여 걷는» 소리 행렬이 곧 나타납니다. <br>오른쪽에서 왼쪽으로 지나가는 소리가 들리지 않는다면 볼륨을 확인해주시고, 스마트폰 환경에서는 진동 모드를 해제해주세요. <br>스크롤을 내리면 참여 방법을 확인하고 소리를 업로드할 수 있으며, 스크롤을 맨 위로 올리거나 오른쪽 위의 글씨를 클릭해 퍼레이드로 다시 돌아올 수 있습니다. <br><br>The sound parade «Walking towards the Flow», appears soon. <br>If you can't hear the sound passing from right to left, check the volume, and if you're using a smartphone, turn off the vibrate mode. <br>Scroll down to see how to join and upload sound, scroll to the top or click the text “parade” on the top right to return to the parade.<br><img class='arrow' src='arrow.svg'>"
|
||||||
|
);
|
||||||
|
intro.class("notice intro"); //-> fadeout & disapear by css animation style.
|
||||||
|
});
|
||||||
|
}, 1000);
|
||||||
|
};
|
||||||
|
b.mouseClicked(starter);
|
||||||
|
d.mouseClicked(starter);
|
||||||
|
roomsel.child(d);
|
||||||
|
roomsel.child(b);
|
||||||
|
} else {
|
||||||
|
//re-connection -> just connect to remembered room!
|
||||||
|
socket.emit("room", myroom, function (res) {
|
||||||
|
if (res) {
|
||||||
|
console.log("entered the room -> " + myroom);
|
||||||
|
} else {
|
||||||
|
console.log("rejected!");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
//var fading_factor = 0.3; //30%
|
||||||
|
var fading_factor = 0.5; //50%
|
||||||
|
socket.on("post", async function (post) {
|
||||||
|
|
||||||
|
console.log(post);
|
||||||
|
var list = await new Promise((resolve, reject) => {
|
||||||
|
loadJSON("/entries", (json) => resolve(json));
|
||||||
|
})
|
||||||
|
console.log(list);
|
||||||
|
|
||||||
|
// var object = post.object;
|
||||||
|
var object = {
|
||||||
|
"id": 1,
|
||||||
|
"type": "abc",
|
||||||
|
"src": "/uploads/" + list[post] + "/pixels.png",
|
||||||
|
"audio": "/uploads/" + list[post] + "/audio.mp3",
|
||||||
|
"alt": "알트",
|
||||||
|
"size": {
|
||||||
|
"base": 40,
|
||||||
|
"random": 20
|
||||||
|
},
|
||||||
|
"y": {
|
||||||
|
"base": 40,
|
||||||
|
"random": 25
|
||||||
|
},
|
||||||
|
"showtime": 40000
|
||||||
|
};
|
||||||
|
console.log(object);
|
||||||
|
|
||||||
|
var img = createImg(object.src, object.alt, "", async function (im) {
|
||||||
|
//로딩이 끝나면, start!
|
||||||
|
var pv = new Tone.PanVol(0, -99).toDestination();
|
||||||
|
var snd = await AudioImport(object.audio); // NOTE: url with spaces didn't work here.
|
||||||
|
snd.connect(pv).start();
|
||||||
|
snd.loop = true;
|
||||||
|
|
||||||
|
//로딩이 끝나면, show!
|
||||||
|
im.show();
|
||||||
|
|
||||||
|
//그림의 크기와 초기 위치 ==> 가로 보기인 경우
|
||||||
|
var width = 0;
|
||||||
|
if (windowWidth > windowHeight) {
|
||||||
|
|
||||||
|
width = (windowHeight * (object.size.base * 0.4 + object.size.random * Math.random())) / 100; // 좀더 크게 + 40% (ratio)
|
||||||
|
im.size(width, AUTO);
|
||||||
|
|
||||||
|
im.position(
|
||||||
|
windowWidth * (1 + fading_factor),
|
||||||
|
(windowHeight * (object.y.base + object.y.random * Math.random()) * 0.5) / 100 // 좀더 위로 위로 - 50% (ratio)
|
||||||
|
);
|
||||||
|
|
||||||
|
//그림의 크기와 초기 위치 ==> 세로 보기인 경우
|
||||||
|
} else {
|
||||||
|
|
||||||
|
width = (windowHeight * (object.size.base + object.size.random * Math.random())) / 100; // json에서 정한 크기 그대로. (ratio)
|
||||||
|
im.size(width, AUTO);
|
||||||
|
|
||||||
|
im.position(
|
||||||
|
windowWidth * (1 + fading_factor),
|
||||||
|
(windowHeight * (object.y.base + object.y.random * Math.random())) / 100 // json에서 정한 위치 그대로. (ratio)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
//추가 정보들
|
||||||
|
im.attribute("data-type", object.type);
|
||||||
|
im.attribute("data-showtime", object.showtime / 1000); //milli-sec. -> seconds.
|
||||||
|
|
||||||
|
//'아이콘' 들은 애니메이션을 시켜줘야 함...
|
||||||
|
if (object.type == "icon") {
|
||||||
|
//
|
||||||
|
im.class("rotate");
|
||||||
|
im.style("animation-duration", object.rotate + "s");
|
||||||
|
var orgs = im.style("transform-origin").split(" ");
|
||||||
|
var str = parseFloat(orgs[0]) + object.pivot.x + "px";
|
||||||
|
str = str + " " + parseFloat(orgs[1]) + object.pivot.y + "px";
|
||||||
|
im.style("transform-origin", str);
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
//로딩이 다 되면, rendering array에 추가.
|
||||||
|
var bundle = {
|
||||||
|
ref: object,
|
||||||
|
img: im,
|
||||||
|
sound: snd,
|
||||||
|
panvol: pv,
|
||||||
|
width: width
|
||||||
|
};
|
||||||
|
arr.push(bundle);
|
||||||
|
});
|
||||||
|
|
||||||
|
//첨에는 hide
|
||||||
|
img.hide();
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
//p5 'draw()' doesn't work if user is not looking at the tab.
|
||||||
|
// --> custom looper is ok.
|
||||||
|
var looper;
|
||||||
|
(looper = function (timeout) {
|
||||||
|
setTimeout(async function () {
|
||||||
|
|
||||||
|
//
|
||||||
|
for (var i = arr.length - 1; i >= 0; i -= 1) {
|
||||||
|
var bundle = arr[i];
|
||||||
|
var img = bundle.img;
|
||||||
|
var showtime = parseFloat(img.attribute("data-showtime"));
|
||||||
|
var type = img.attribute("data-type");
|
||||||
|
var x = img.position().x;
|
||||||
|
var y = img.position().y;
|
||||||
|
y = y + random(-1, 1);
|
||||||
|
x = x - windowWidth / (fr * showtime);
|
||||||
|
|
||||||
|
//
|
||||||
|
if (type == "icon") {
|
||||||
|
img.style("z-index", "-1");
|
||||||
|
}
|
||||||
|
3;
|
||||||
|
|
||||||
|
img.position(x, y);
|
||||||
|
var pan = (x / windowWidth) * 2 - 1;
|
||||||
|
|
||||||
|
//panning
|
||||||
|
var snd = bundle.sound;
|
||||||
|
var pv = bundle.panvol;
|
||||||
|
if (x >= -bundle.width && x < windowWidth) {
|
||||||
|
pan = ((x + bundle.width) / (windowWidth + bundle.width)) * 2 - 1;
|
||||||
|
pv.pan.value = pan;
|
||||||
|
pv.volume.value = 0;//(dB)
|
||||||
|
} else {
|
||||||
|
var range;
|
||||||
|
var knob;
|
||||||
|
if (x >= windowWidth) {
|
||||||
|
range = windowWidth * fading_factor
|
||||||
|
knob = x - windowWidth;
|
||||||
|
pv.pan.value = 1;
|
||||||
|
pv.volume.value = knob / range * -20;
|
||||||
|
} else if (x < -bundle.width) {
|
||||||
|
range = windowWidth * fading_factor
|
||||||
|
knob = (x + bundle.width) * -1;
|
||||||
|
pv.pan.value = -1;
|
||||||
|
pv.volume.value = knob / range * -20;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//scroll down -> volume down
|
||||||
|
// if (userscroll > windowHeight*2) {
|
||||||
|
// pv.volume.value = 0; //off
|
||||||
|
// } else if (userscroll >= windowHeight && userscroll < windowHeight*2) {
|
||||||
|
// pv.volume.value = (1 - (userscroll - windowHeight)/windowHeight) * pv.volume.value; //fade out
|
||||||
|
// }
|
||||||
|
|
||||||
|
//remove with sound fade-out
|
||||||
|
var exit_x = -bundle.width - windowWidth * fading_factor;
|
||||||
|
if (x < exit_x) {
|
||||||
|
img.remove();
|
||||||
|
snd.stop();
|
||||||
|
delete snd;
|
||||||
|
delete pv;
|
||||||
|
arr.splice(i, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//
|
||||||
|
|
||||||
|
looper(1000 / fr);
|
||||||
|
}, timeout);
|
||||||
|
})(1000 / fr);
|
||||||
|
|
||||||
|
// function randomvoiceplay() {
|
||||||
|
// (looper = function(timeout) {
|
||||||
|
// setTimeout(function() {
|
||||||
|
// voice[int(random(19))].play();
|
||||||
|
// looper(random(8000, 12000));
|
||||||
|
// }, timeout);
|
||||||
|
// })(8000);
|
||||||
|
// }
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
Loading…
Reference in a new issue