An online landscape, built as a tool to explore the many aspects of the human voice.
https://voicegardens.org
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
348 lines
7.2 KiB
348 lines
7.2 KiB
"use strict";
|
|
|
|
//
|
|
// Voicegardens front-end Javascript
|
|
//
|
|
|
|
var archiveUrl = window.location + "add-to-archive";
|
|
var canvas;
|
|
var centerX;
|
|
var centerY;
|
|
var frameRate = 30;
|
|
var microphone;
|
|
var newSoundJustRecorded = false;
|
|
var playButton;
|
|
var recordButton;
|
|
var recorder;
|
|
var recording;
|
|
var recordingTimeout = 30000; // 30 seconds (in milliseconds)
|
|
var shapes = [];
|
|
var stopButton;
|
|
var canvasWidth;
|
|
var canvasHeight;
|
|
var timer = 0;
|
|
var secondTick = false;
|
|
|
|
function record() {
|
|
/**
|
|
* Starting recording.
|
|
**/
|
|
if (microphone.enabled) {
|
|
setTimeout(recorder.record(recording), recordingTimeout);
|
|
}
|
|
}
|
|
|
|
function stop() {
|
|
/**
|
|
* Stop recording a new recording.
|
|
**/
|
|
if (recorder.recording) {
|
|
recorder.stop();
|
|
newSoundJustRecorded = true;
|
|
}
|
|
}
|
|
|
|
function play() {
|
|
/**
|
|
* Play the recording.
|
|
**/
|
|
if (recording.isLoaded()) {
|
|
recording.play();
|
|
}
|
|
}
|
|
|
|
function archive() {
|
|
/**
|
|
* Send the recording to the back-end.
|
|
**/
|
|
var soundBlob = recording.getBlob();
|
|
|
|
var formData = new FormData();
|
|
var date = new Date();
|
|
var filename = date.getTime().toString() + ".wav";
|
|
formData.append("file", soundBlob, filename);
|
|
|
|
var config = new Headers({ "Content-Type": "multipart/form-data" });
|
|
|
|
axios.post(archiveUrl, formData, config).catch(function(error) {
|
|
console.log(
|
|
"Upload failed!",
|
|
"Received the following message:",
|
|
error.response.data
|
|
);
|
|
});
|
|
}
|
|
|
|
function showArchive() {
|
|
window.location.href = "/archive";
|
|
}
|
|
|
|
function setupRecording() {
|
|
/**
|
|
* Setup logic for recording.
|
|
**/
|
|
microphone = new p5.AudioIn();
|
|
microphone.start();
|
|
|
|
recorder = new p5.SoundRecorder();
|
|
recorder.setInput(microphone);
|
|
|
|
recording = new p5.SoundFile();
|
|
|
|
recordButton = createButton("record");
|
|
recordButton.position(10, 5);
|
|
recordButton.mousePressed(record);
|
|
|
|
stopButton = createButton("stop");
|
|
stopButton.position(10, 40);
|
|
stopButton.mousePressed(stop);
|
|
|
|
playButton = createButton("play");
|
|
playButton.position(10, 75);
|
|
playButton.mousePressed(play);
|
|
|
|
playButton = createButton("archive");
|
|
playButton.position(10, 110);
|
|
playButton.mousePressed(archive);
|
|
|
|
playButton = createButton("view archive");
|
|
playButton.position(10, 145);
|
|
playButton.mousePressed(showArchive);
|
|
}
|
|
|
|
function getSoundInfo() {
|
|
/**
|
|
* Retrieve sound information like pitch, amplitude, duration, etc.
|
|
**/
|
|
amplitude = recording.getPeaks();
|
|
duration = recording.duration();
|
|
|
|
// pitch (frequency?) I think we can use fft.analyze() and then find the
|
|
// highest value (0 -> 1024) that has a non-zero value this gives us the
|
|
// highest frequency from the recording
|
|
// https://p5js.org/reference/#/p5.FFT
|
|
// https://p5js.org/reference/#/p5.FFT/analyze
|
|
|
|
// nuance?
|
|
// "I meant the amount of variation in the voice - i.e is it one single
|
|
// monotone note or does it go up and down octaves or start soft and high and
|
|
// become deep and guttural etc."
|
|
//
|
|
// How do to do this? Unsure ...
|
|
}
|
|
|
|
class GeneratedShape {
|
|
constructor() {
|
|
/**
|
|
* Initialise the new shape.
|
|
**/
|
|
this.synth = new p5.MonoSynth();
|
|
|
|
this.opacity = 0;
|
|
this.colour = this.chooseColour();
|
|
|
|
this.accelX = 0.0;
|
|
this.accelY = 0.0;
|
|
|
|
this.deltaX = 0.0;
|
|
this.deltaY = 0.0;
|
|
|
|
this.springing = 0.0009;
|
|
this.damping = 0.98;
|
|
this.organicConstant = random(-5, 5);
|
|
|
|
this.startXs = [];
|
|
this.startYs = [];
|
|
|
|
this.xs = [];
|
|
this.ys = [];
|
|
|
|
this.angle = [];
|
|
this.frequency = [];
|
|
|
|
this.edges = 5;
|
|
|
|
this.radius = random(10, 180);
|
|
this.rotAngle = random(10, 140);
|
|
|
|
this.randomX = random(-77, 77);
|
|
this.randomY = random(-77, 77);
|
|
|
|
this.destX = random(canvasWidth);
|
|
this.destY = random(canvasHeight);
|
|
|
|
this.centerX = centerX;
|
|
this.centerY = centerY;
|
|
|
|
this.initialise();
|
|
}
|
|
|
|
initialise() {
|
|
/**
|
|
* Initialise all movement related arrays.
|
|
**/
|
|
for (let i = 0; i < this.edges; i++) {
|
|
this.startXs[i] = 0;
|
|
this.startYs[i] = 0;
|
|
this.ys[i] = 0;
|
|
this.xs[i] = 0;
|
|
this.angle[i] = 0;
|
|
}
|
|
for (let i = 0; i < this.edges; i++) {
|
|
this.frequency[i] = random(5, 12);
|
|
}
|
|
}
|
|
|
|
collide(shapes) {
|
|
/**
|
|
* Detect if the shape collides with another shape.
|
|
**/
|
|
// TODO: implement once again
|
|
return false;
|
|
}
|
|
|
|
sound() {
|
|
/**
|
|
* Play a sound after a collision is detected.
|
|
**/
|
|
// TODO: implement once again
|
|
return;
|
|
}
|
|
|
|
fadein() {
|
|
/**
|
|
* Fade-in the shape using alpha values.
|
|
**/
|
|
if (this.opacity < 256) {
|
|
let currentAlpha = this.colour._getAlpha();
|
|
this.colour.setAlpha(currentAlpha + random(0, 3));
|
|
} else {
|
|
this.opacity = 256;
|
|
}
|
|
}
|
|
|
|
chooseColour() {
|
|
/**
|
|
* Choose a colour for the shape.
|
|
**/
|
|
let colourChoices = [
|
|
color("red"),
|
|
color("blue"),
|
|
color("green"),
|
|
color("black"),
|
|
color("white")
|
|
];
|
|
let index = floor(random(0, colourChoices.length));
|
|
let chosenColour = colourChoices[index];
|
|
chosenColour.setAlpha(this.opacity);
|
|
return chosenColour;
|
|
}
|
|
|
|
draw() {
|
|
/**
|
|
* Draw the shape vectors.
|
|
**/
|
|
if (this.opacity != 256) this.fadein();
|
|
fill(this.colour);
|
|
|
|
for (let i = 0; i < this.edges; i++) {
|
|
this.startXs[i] =
|
|
this.centerX +
|
|
cos(radians(this.rotAngle) * i) * this.radius +
|
|
this.randomX;
|
|
this.startYs[i] =
|
|
this.centerY +
|
|
sin(radians(this.rotAngle) * i) * this.radius +
|
|
this.randomY;
|
|
this.rotAngle += 360 / this.edges;
|
|
}
|
|
|
|
curveTightness(this.organicConstant);
|
|
beginShape();
|
|
for (let i = 0; i < this.edges; i++) {
|
|
curveVertex(this.xs[i], this.ys[i]);
|
|
}
|
|
endShape(CLOSE);
|
|
}
|
|
|
|
move() {
|
|
/**
|
|
* Move the shape vectors.
|
|
**/
|
|
this.deltaX = this.destX - this.centerX;
|
|
this.deltaY = this.destY - this.centerY;
|
|
|
|
this.deltaX *= this.springing;
|
|
this.deltaY *= this.springing;
|
|
|
|
this.accelX += this.deltaX;
|
|
this.accelY += this.deltaY;
|
|
|
|
this.centerX += this.accelX;
|
|
this.centerY += this.accelY;
|
|
|
|
this.accelX *= this.damping;
|
|
this.accelY *= this.damping;
|
|
|
|
this.organicConstant = 1 - (abs(this.accelX) + abs(this.accelY)) * 0.1;
|
|
|
|
for (let i = 0; i < this.edges; i++) {
|
|
this.xs[i] =
|
|
this.startXs[i] + sin(radians(this.angle[i])) * (this.accelX * 2);
|
|
this.ys[i] =
|
|
this.startYs[i] + sin(radians(this.angle[i])) * (this.accelY * 2);
|
|
this.angle[i] += this.frequency[i];
|
|
}
|
|
}
|
|
}
|
|
|
|
function setup() {
|
|
/**
|
|
* The p5.js initial setup function.
|
|
**/
|
|
canvasWidth = 600;
|
|
canvasHeight = 600;
|
|
|
|
createCanvas(canvasWidth, canvasHeight);
|
|
frameRate(frameRate);
|
|
|
|
setupRecording();
|
|
|
|
centerX = canvasWidth / 2;
|
|
centerY = canvasHeight / 2;
|
|
}
|
|
|
|
function draw() {
|
|
/**
|
|
* The p5.js draw loop.
|
|
**/
|
|
background("#69D2E7");
|
|
blendMode(BLEND);
|
|
smooth();
|
|
noStroke();
|
|
|
|
if (newSoundJustRecorded === true) {
|
|
shapes.push(new GeneratedShape());
|
|
newSoundJustRecorded = false;
|
|
}
|
|
|
|
if (millis() >= 2000 + timer) {
|
|
secondTick = true;
|
|
timer = millis();
|
|
}
|
|
|
|
for (var shape of shapes) {
|
|
shape.draw();
|
|
shape.move();
|
|
|
|
if (secondTick) {
|
|
shape.destX = random(canvasWidth);
|
|
shape.destY = random(canvasHeight);
|
|
}
|
|
|
|
if (shape.collide(shapes) === true) {
|
|
shape.sound();
|
|
}
|
|
}
|
|
secondTick = false;
|
|
}
|
|
|