wtc_plaza_2001-09-11.js (8424B)
1 'use strict'; 2 3 // Why is it not part of the standard yet? 4 Math.TAU = 2 * Math.PI; 5 6 (function() { 7 let audio_context; 8 let scream_audio_data; 9 let thump_audio_data; 10 11 let BLOOD_COLOR = (0, 0, 255, 255); 12 let STICK_FIGURE_COLOR = (0, 0, 0, 255); 13 14 function lerp(portion, start, stop) { 15 return portion * (stop - start) + start; 16 } 17 18 function make_random_direction() { 19 let uniform = Math.random(); 20 return [ 21 Math.cos(Math.TAU * uniform), 22 Math.sin(Math.TAU * uniform), 23 ]; 24 }; 25 26 function add_random_position(original_position, distance) { 27 let new_direction = make_random_direction(); 28 29 return [ 30 original_position[0] + distance * new_direction[0], 31 original_position[1] + distance * new_direction[1], 32 ]; 33 } 34 35 function make_random_stick_figure_positions() { 36 let head_neck_distance = 0.05; 37 let elbow_neck_distance = 0.050; 38 let arm_tip_elbow_distance = 0.050; 39 let pelvis_neck_distance = 0.125; 40 let knee_pelvis_distance = 0.075; 41 let leg_tip_knee_distance = 0.075; 42 43 let head_position = [Math.random(), Math.random()]; 44 45 let neck_position = add_random_position(head_position, head_neck_distance); 46 47 let left_elbow_position = add_random_position(neck_position, elbow_neck_distance); 48 let right_elbow_position = add_random_position(neck_position, elbow_neck_distance); 49 50 let left_arm_tip_position = add_random_position(left_elbow_position, arm_tip_elbow_distance); 51 let right_arm_tip_position = add_random_position(right_elbow_position, arm_tip_elbow_distance); 52 53 let pelvis_position = add_random_position(neck_position, pelvis_neck_distance); 54 55 let left_knee_position = add_random_position(pelvis_position, knee_pelvis_distance); 56 let right_knee_position = add_random_position(pelvis_position, knee_pelvis_distance); 57 58 let left_leg_top_position = add_random_position(left_knee_position, leg_tip_knee_distance); 59 let right_leg_top_position = add_random_position(right_knee_position, leg_tip_knee_distance); 60 61 return [ 62 head_position, 63 neck_position, 64 left_elbow_position, 65 right_elbow_position, 66 left_arm_tip_position, 67 right_arm_tip_position, 68 pelvis_position, 69 left_knee_position, 70 right_knee_position, 71 left_leg_top_position, 72 right_leg_top_position, 73 ]; 74 } 75 76 77 78 79 function make_blood_splatter_positions(stick_figure_positions) { 80 let blood_splatter_positions = []; 81 82 for (let i = 0; i < 3; i++) { 83 blood_splatter_positions.push( 84 add_random_position(stick_figure_positions[1], 0.05 * (i + 1)) 85 ); 86 } 87 88 for (let i = 0; i < 3; i++) { 89 blood_splatter_positions.push( 90 add_random_position(stick_figure_positions[6], 0.05 * (i + 1)) 91 ); 92 } 93 94 return blood_splatter_positions; 95 } 96 97 function draw_blood_splatter(blood_splatter_positions) { 98 let canvas = document.getElementById('canvas'); 99 let ctx = canvas.getContext('2d'); 100 101 let image_width = ctx.canvas.width; 102 let image_height = ctx.canvas.height; 103 104 let blood_splatter_positions_in_image = blood_splatter_positions.map( 105 position => [ 106 Math.floor(position[0] * image_width), 107 Math.floor(position[1] * image_height), 108 ] 109 ); 110 111 112 ctx.fillStyle = '#f00'; 113 114 ctx.beginPath(); 115 ctx.arc(blood_splatter_positions_in_image[0][0], blood_splatter_positions_in_image[0][1], 20, 0, Math.TAU); 116 ctx.fill(); 117 118 ctx.beginPath(); 119 ctx.arc(blood_splatter_positions_in_image[1][0], blood_splatter_positions_in_image[1][1], 10, 0, Math.TAU); 120 ctx.fill(); 121 122 ctx.beginPath(); 123 ctx.arc(blood_splatter_positions_in_image[2][0], blood_splatter_positions_in_image[2][1], 5, 0, Math.TAU); 124 ctx.fill(); 125 126 ctx.beginPath(); 127 ctx.arc(blood_splatter_positions_in_image[3][0], blood_splatter_positions_in_image[3][1], 20, 0, Math.TAU); 128 ctx.fill(); 129 130 ctx.beginPath(); 131 ctx.arc(blood_splatter_positions_in_image[4][0], blood_splatter_positions_in_image[4][1], 10, 0, Math.TAU); 132 ctx.fill(); 133 134 ctx.beginPath(); 135 ctx.arc(blood_splatter_positions_in_image[5][0], blood_splatter_positions_in_image[5][1], 5, 0, Math.TAU); 136 ctx.fill(); 137 138 ctx.fill() 139 } 140 141 142 function draw_stick_figure(stick_figure_positions) { 143 let canvas = document.getElementById('canvas'); 144 let ctx = canvas.getContext('2d'); 145 146 let image_width = ctx.canvas.width; 147 let image_height = ctx.canvas.height; 148 149 let stick_figure_positions_in_image = stick_figure_positions.map( 150 x => [ 151 Math.floor(x[0] * image_width), 152 Math.floor(x[1] * image_height), 153 ] 154 ); 155 console.debug(stick_figure_positions_in_image) 156 157 ctx.fillStyle = '#000'; 158 ctx.strokeStyle = '#000'; 159 ctx.lineWidth = 2; 160 161 ctx.beginPath(); 162 ctx.arc( 163 stick_figure_positions_in_image[0][0], 164 stick_figure_positions_in_image[0][1], 165 15, 166 0, 167 Math.TAU, 168 false); 169 ctx.fill(); 170 171 let connections = [ 172 [0, 1], 173 [1, 2], 174 [1, 3], 175 [2, 4], 176 [3, 5], 177 [1, 6], 178 [7, 6], 179 [8, 6], 180 [7, 9], 181 [8, 10], 182 ]; 183 for (let i = 0; i < connections.length; i++) { 184 ctx.beginPath(); 185 let origin_coordinates = stick_figure_positions_in_image[connections[i][0]]; 186 let destination_coordinates = stick_figure_positions_in_image[connections[i][1]]; 187 console.debug([origin_coordinates, destination_coordinates]) 188 ctx.moveTo(origin_coordinates[0], origin_coordinates[1]); 189 ctx.lineTo(destination_coordinates[0], destination_coordinates[1]); 190 ctx.stroke(); 191 } 192 } 193 194 function draw_jumper() { 195 let stick_figure_positions = make_random_stick_figure_positions(); 196 let blood_splatter_positions = make_blood_splatter_positions(stick_figure_positions); 197 198 console.debug(stick_figure_positions); 199 console.debug(blood_splatter_positions); 200 201 draw_blood_splatter(blood_splatter_positions); 202 draw_stick_figure(stick_figure_positions); 203 } 204 205 206 function jump() { 207 let scream_rate_fraction = lerp(Math.random(), 0.90, 1.45); 208 let scream_audio_buffer = make_audio_buffer(scream_audio_data, scream_rate_fraction); 209 let scream_source = make_audio_buffer_source(scream_audio_buffer); 210 scream_source.start(); 211 212 setTimeout(function() { 213 scream_source.stop(); 214 215 let thump_audio_buffer = make_audio_buffer(thump_audio_data, 1); 216 let thump_audio_source = make_audio_buffer_source(thump_audio_buffer); 217 218 thump_audio_source.currentTime = 0.2; 219 220 thump_audio_source.start() 221 222 draw_jumper(); 223 }, 1700.0 / scream_rate_fraction); 224 } 225 226 function clear_canvas() { 227 let canvas = document.getElementById('canvas'); 228 let ctx = canvas.getContext('2d'); 229 230 ctx.clearRect(0, 0, canvas.width, canvas.height); 231 } 232 233 async function get_audio_data(audio_file_path) { 234 let response = await fetch(audio_file_path); 235 236 let array_buffer = await response.arrayBuffer(); 237 238 return await audio_context.decodeAudioData(array_buffer); 239 } 240 241 function make_audio_buffer(audio_data, sample_rate_fraction) { 242 let audio_buffer = audio_context.createBuffer( 243 audio_data.numberOfChannels, 244 audio_data.getChannelData(0).length, 245 audio_data.sampleRate * sample_rate_fraction, 246 ); 247 248 for (let channel_number = 0; channel_number < audio_buffer.numberOfChannels; channel_number++) { 249 let current_buffer = audio_buffer.getChannelData(channel_number); 250 let audio_data_channel = audio_data.getChannelData(channel_number); 251 252 for (let sample_number = 0; sample_number < current_buffer.length; sample_number++) { 253 current_buffer[sample_number] = audio_data_channel[sample_number]; 254 } 255 } 256 257 return audio_buffer; 258 } 259 260 function make_audio_buffer_source(audio_buffer) { 261 let buffer_source = audio_context.createBufferSource(); 262 263 buffer_source.buffer = audio_buffer; 264 265 buffer_source.connect(audio_context.destination); 266 267 return buffer_source; 268 } 269 270 async function main() { 271 audio_context = new AudioContext(); 272 273 let scream_audio_element = document.getElementById('scream_audio'); 274 let thump_audio_element = document.getElementById('thump_audio'); 275 276 scream_audio_data = await get_audio_data(scream_audio_element.src); 277 thump_audio_data = await get_audio_data(thump_audio_element.src); 278 279 let canvas = document.getElementById('canvas'); 280 canvas.addEventListener('click', jump); 281 282 let jump_button = document.getElementById('jump_button'); 283 jump_button.addEventListener('click', jump); 284 285 let clear_button = document.getElementById('clear_button'); 286 clear_button.addEventListener('click', clear_canvas); 287 } 288 289 window.addEventListener('load', main); 290 })();