712 lines
24 KiB
HTML
712 lines
24 KiB
HTML
<!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;
|
||
}
|
||
|
||
#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</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://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="보내기!" />
|
||
</li>
|
||
</ul>
|
||
<p>
|
||
↓
|
||
</p>
|
||
</form>
|
||
</section>
|
||
<section>
|
||
<h1>소리 목록</h1>
|
||
<p>제출해주신 소리는 아래 목록에 시간순으로 쌓입니다. 각각의 모양을 클릭하면, 모양에 해당하는 소리를 듣고 소리에 대한 정보를 확인할 수 있습니다.</p>
|
||
{{#each list}}
|
||
<div class="items" foldername="{{this.foldername}}">
|
||
<details>
|
||
<summary><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);
|
||
// }
|
||
|
||
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();
|
||
request.open("POST", "/entry");
|
||
request.onload = () => {
|
||
alert('감사합니다!');
|
||
location.reload();
|
||
//location.assign("/submit");
|
||
}
|
||
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();
|
||
} 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 = 30;
|
||
} 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
|
||
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;
|
||
});
|
||
}
|
||
|
||
//**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!");
|
||
|
||
//
|
||
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");
|
||
b.mouseClicked(function () {
|
||
silence.start();
|
||
clap.start();
|
||
myroom = parseInt(this.value());
|
||
|
||
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 Toward the Flow» will appear soon. <br>If there is no sound, check the volume and turn off the vibration mode in the smartphone environment.<br> You can see how to participate and upload sound by scrolling down, and you can return to the parade by clicking on the text in the upper right corner.<br><img class='arrow' src='arrow.svg'>"
|
||
);
|
||
intro.class("notice intro"); //-> fadeout & disapear by css animation style.
|
||
});
|
||
}, 1000);
|
||
});
|
||
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": 20,
|
||
"random": 10
|
||
},
|
||
"showtime": 20000
|
||
};
|
||
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 * 1.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;
|
||
}
|
||
}
|
||
|
||
//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> |