-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathwavepluck.html
More file actions
154 lines (145 loc) · 5.18 KB
/
wavepluck.html
File metadata and controls
154 lines (145 loc) · 5.18 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Wave Pluck</title>
<meta name="description" content="Plucked oscilloscope test.">
<meta name="keywords" content="Audio, Oscilloscope, Reverse, Spring, Waveform">
<meta property="og:title" content="Wave Pluck">
<meta property="og:type" content="website">
<meta property="og:url" content="https://mysterypancake.github.io/Waveform/wavepluck">
<meta property="og:site_name" content="Wave Pluck">
<meta property="og:description" content="Plucked oscilloscope test.">
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
html, body {
margin: 0;
overflow: hidden;
padding: 0;
}
canvas {
display: block;
}
</style>
<script>
var canvas;
var player;
var context;
var frame = 0;
var pitch = 0;
var springs = [];
var requestFrame = window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame || function(e) { return window.setTimeout(e, 1000 / 60); };
var cancelFrame = window.cancelAnimationFrame || window.webkitCancelAnimationFrame || window.mozCancelAnimationFrame || window.oCancelAnimationFrame || window.msCancelAnimationFrame || function(id) { window.clearTimeout(id); };
var params = {
dampening: 0.1,
tension: 0.1,
passes: 32,
spread: 0.3
};
// From https://gamedevelopment.tutsplus.com/tutorials/make-a-splash-with-dynamic-2d-water-effects--gamedev-236
function spring(height) {
this.height = height;
this.target = height;
this.velocity = 0;
this.update = function(tension, dampening) {
this.velocity += tension * (this.target - this.height) - (this.velocity * dampening);
this.height += this.velocity;
};
}
function updateSprings(springs, tension, dampening, passes, spread) {
for (var i = 0; i < springs.length; i++) {
springs[i].update(tension, dampening);
}
var leftDeltas = [];
var rightDeltas = [];
for (var j = 0; j < passes; j++) {
for (var i = 0; i < springs.length; i++) {
if (i > 0) {
leftDeltas[i] = spread * (springs[i].height - springs[i - 1].height);
springs[i - 1].velocity += leftDeltas[i];
}
if (i < springs.length - 1) {
rightDeltas[i] = spread * (springs[i].height - springs[i + 1].height);
springs[i + 1].velocity += rightDeltas[i];
}
}
for (var i = 0; i < springs.length; i++) {
if (i > 0) {
springs[i - 1].height += leftDeltas[i];
}
if (i < springs.length - 1) {
springs[i + 1].height += rightDeltas[i];
}
}
}
}
function setup() {
for (var i = 0; i < 1024; i++) {
springs.push(new spring(0));
}
canvas = document.getElementById("canvas");
context = canvas.getContext("2d", { alpha: false });
window.addEventListener("resize", resize);
window.addEventListener("orientationchange", resize);
resize();
window.addEventListener("mousedown", clicked);
window.addEventListener("contextmenu", hide);
}
function hide(e) {
e.preventDefault();
}
function clicked(e) {
e.preventDefault();
// AudioContext must be created after interaction with the website
if (!player) {
player = new (window.AudioContext || window.webkitAudioContext)();
// Buffer size is locked to spring size because I'm too lazy to make a circular buffer
// As a consequence, the pitch has to be a multiple to avoid artifacts near edges
var node = player.createScriptProcessor(springs.length, 1, 1);
node.onaudioprocess = function(e) {
updateSprings(springs, params.tension, params.dampening, params.passes, params.spread);
var output = e.outputBuffer.getChannelData(0);
for (var i = 0; i < springs.length; i++) {
output[i] = (springs[i * (pitch + 1) % springs.length].height) * 0.01;
}
};
node.connect(player.destination);
}
// Fake changing the length of the string via resampling
pitch = (pitch + 1) % 4;
if (e.button == 2) {
// Right click plucks only one spring
var nearest = Math.floor(e.pageX / canvas.width * (springs.length - 1));
springs[nearest].velocity += (e.pageY - (canvas.height * 0.5)) * 8;
} else {
// Left click plucks all springs at once, except the first and last 64 to reduce artifacts near edges
// Artifacts could be reduced with a windowing function or adding extra springs past the edges to overlap-add
for (var i = 64; i < springs.length - 64; i++) {
springs[i].velocity += (e.pageY - canvas.height * 0.5) * (Math.random() - 0.5) * 2;
}
}
}
function resize() {
cancelFrame(frame);
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
frame = requestFrame(draw);
}
function draw() {
context.fillStyle = "black";
context.fillRect(0, 0, canvas.width, canvas.height);
context.strokeStyle = "white";
context.lineWidth = 2;
context.beginPath();
for (var i = 0; i < springs.length; i++) {
context.lineTo(canvas.width * i / (springs.length - 1), springs[i].height + (canvas.height * 0.5));
}
context.stroke();
frame = requestFrame(draw);
}
</script>
</head>
<body onload="setup();">
<canvas id="canvas"></canvas>
</body>
</html>