|
@ -4,31 +4,56 @@ |
|
|
// Voicegardens front-end Javascript
|
|
|
// Voicegardens front-end Javascript
|
|
|
//
|
|
|
//
|
|
|
|
|
|
|
|
|
|
|
|
// URL which exposes the archive saving API end-point
|
|
|
var archiveUrl = window.location + "add-to-archive"; |
|
|
var archiveUrl = window.location + "add-to-archive"; |
|
|
var canvasHeight; |
|
|
|
|
|
var canvasWidth; |
|
|
// The x,y coordinates which gives the center of the canvas
|
|
|
var centerX; |
|
|
var centerX; |
|
|
var centerY; |
|
|
var centerY; |
|
|
|
|
|
|
|
|
|
|
|
// The canvas frame rate which controls how many times the draw function is
|
|
|
|
|
|
// called. So far as we have seen, this can affect performance. Try the value
|
|
|
|
|
|
// of 30 and also 60 to see the difference. Higher values may also give more
|
|
|
|
|
|
// fun animation effects.
|
|
|
var frameRate = 30; |
|
|
var frameRate = 30; |
|
|
|
|
|
|
|
|
|
|
|
// Sound recording API objects
|
|
|
var microphone; |
|
|
var microphone; |
|
|
|
|
|
var recorder; |
|
|
|
|
|
var recording; |
|
|
|
|
|
|
|
|
|
|
|
// Boolean which is only true when the user stops the recording of a sound.
|
|
|
|
|
|
// This then triggers the generation of a shape based on that recording.
|
|
|
var newSoundJustRecorded = false; |
|
|
var newSoundJustRecorded = false; |
|
|
|
|
|
|
|
|
|
|
|
// All user clickable buttons
|
|
|
|
|
|
var archiveButton; |
|
|
var playButton; |
|
|
var playButton; |
|
|
var recordButton; |
|
|
var recordButton; |
|
|
var recorder; |
|
|
|
|
|
var recording; |
|
|
|
|
|
var recordingTimeout = 30000; // 30 seconds (in milliseconds)
|
|
|
|
|
|
var screenX; |
|
|
|
|
|
var screenY; |
|
|
|
|
|
var secondTick = false; |
|
|
|
|
|
var shapes = []; |
|
|
|
|
|
var stopButton; |
|
|
var stopButton; |
|
|
var timer = 0; |
|
|
var viewArchiveButton; |
|
|
var toScreenX; |
|
|
|
|
|
var toScreenY; |
|
|
// Time-out used to stop a recording in case the user forgets to stop it
|
|
|
|
|
|
// themselves. It also gurantees to turn off the mic in case of some unknown
|
|
|
|
|
|
// error that we didn't take into consideration. This is 30 seconds in
|
|
|
|
|
|
// milliseconds.
|
|
|
|
|
|
var recordingTimeout = 30000; |
|
|
|
|
|
|
|
|
|
|
|
// The x,y coordinates which shows where the window position is. This is useful
|
|
|
|
|
|
// because we use the `translate` function to offset the window view in
|
|
|
|
|
|
// relation to the canvas (users can drag their view across the "environment")
|
|
|
|
|
|
// and we need to record where that x position leaves us.
|
|
|
|
|
|
var screenX = 0; |
|
|
|
|
|
var screenY = 0; |
|
|
|
|
|
var toScreenX = 0; |
|
|
|
|
|
var toScreenY = 0; |
|
|
|
|
|
|
|
|
|
|
|
// All shapes generated
|
|
|
|
|
|
var shapes = []; |
|
|
|
|
|
|
|
|
function record() { |
|
|
function record() { |
|
|
/** |
|
|
/** |
|
|
* Starting recording. |
|
|
* Start recording a sound. |
|
|
**/ |
|
|
**/ |
|
|
if (microphone.enabled) { |
|
|
if (microphone.enabled) { |
|
|
setTimeout(recorder.record(recording), recordingTimeout); |
|
|
setTimeout(recorder.record(recording), recordingTimeout); |
|
@ -37,17 +62,19 @@ function record() { |
|
|
|
|
|
|
|
|
function stop() { |
|
|
function stop() { |
|
|
/** |
|
|
/** |
|
|
* Stop recording a new recording. |
|
|
* Stop recording a new sound. |
|
|
**/ |
|
|
**/ |
|
|
if (recorder.recording) { |
|
|
if (recorder.recording) { |
|
|
recorder.stop(); |
|
|
recorder.stop(); |
|
|
|
|
|
|
|
|
|
|
|
// signal to the draw loop that we should generate a new shape
|
|
|
newSoundJustRecorded = true; |
|
|
newSoundJustRecorded = true; |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
function play() { |
|
|
function play() { |
|
|
/** |
|
|
/** |
|
|
* Play the recording. |
|
|
* Play the sound just recorded. |
|
|
**/ |
|
|
**/ |
|
|
if (recording.isLoaded()) { |
|
|
if (recording.isLoaded()) { |
|
|
recording.play(); |
|
|
recording.play(); |
|
@ -56,7 +83,7 @@ function play() { |
|
|
|
|
|
|
|
|
function archive() { |
|
|
function archive() { |
|
|
/** |
|
|
/** |
|
|
* Send the recording to the back-end. |
|
|
* Send the sound to the back-end for archving. |
|
|
**/ |
|
|
**/ |
|
|
var soundBlob = recording.getBlob(); |
|
|
var soundBlob = recording.getBlob(); |
|
|
|
|
|
|
|
@ -77,12 +104,15 @@ function archive() { |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
function showArchive() { |
|
|
function showArchive() { |
|
|
|
|
|
/** |
|
|
|
|
|
* Set URL to view archive. Called by the `archiveButton`. |
|
|
|
|
|
**/ |
|
|
window.location.href = "/archive"; |
|
|
window.location.href = "/archive"; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
function setupRecording() { |
|
|
function setupRecording() { |
|
|
/** |
|
|
/** |
|
|
* Setup logic for recording. |
|
|
* Setup logic for sound recording. |
|
|
**/ |
|
|
**/ |
|
|
microphone = new p5.AudioIn(); |
|
|
microphone = new p5.AudioIn(); |
|
|
microphone.start(); |
|
|
microphone.start(); |
|
@ -104,13 +134,13 @@ function setupRecording() { |
|
|
playButton.position(10, 75); |
|
|
playButton.position(10, 75); |
|
|
playButton.mousePressed(play); |
|
|
playButton.mousePressed(play); |
|
|
|
|
|
|
|
|
playButton = createButton("archive"); |
|
|
archiveButton = createButton("archive"); |
|
|
playButton.position(10, 110); |
|
|
archiveButton.position(10, 110); |
|
|
playButton.mousePressed(archive); |
|
|
archiveButton.mousePressed(archive); |
|
|
|
|
|
|
|
|
playButton = createButton("view archive"); |
|
|
viewArchiveButton = createButton("view archive"); |
|
|
playButton.position(10, 145); |
|
|
viewArchiveButton.position(10, 145); |
|
|
playButton.mousePressed(showArchive); |
|
|
viewArchiveButton.mousePressed(showArchive); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
function getSoundInfo() { |
|
|
function getSoundInfo() { |
|
@ -139,41 +169,71 @@ class GeneratedShape { |
|
|
/** |
|
|
/** |
|
|
* Initialise the new shape. |
|
|
* Initialise the new shape. |
|
|
**/ |
|
|
**/ |
|
|
this.synth = new p5.MonoSynth(); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// The opacity of the shape. This controls whether we can see the shape or
|
|
|
|
|
|
// not (transparency). It starts at zero as we want to fade the shapes in
|
|
|
|
|
|
// when they enter the environment
|
|
|
this.opacity = 0; |
|
|
this.opacity = 0; |
|
|
|
|
|
|
|
|
|
|
|
// the colour of the shape
|
|
|
this.colour = this.chooseColour(); |
|
|
this.colour = this.chooseColour(); |
|
|
|
|
|
|
|
|
|
|
|
// Acceleration x,y values which control at which speed the shape
|
|
|
|
|
|
// accelerates towards the intended x,y destination.
|
|
|
this.accelX = 0.0; |
|
|
this.accelX = 0.0; |
|
|
this.accelY = 0.0; |
|
|
this.accelY = 0.0; |
|
|
|
|
|
|
|
|
|
|
|
// The x, y destination values which the shape moves towards. This can be
|
|
|
|
|
|
// calculated against the `mouseX` and `mouseY` values, for example. Then
|
|
|
|
|
|
// the shape will follow the mouse.
|
|
|
this.deltaX = 0.0; |
|
|
this.deltaX = 0.0; |
|
|
this.deltaY = 0.0; |
|
|
this.deltaY = 0.0; |
|
|
|
|
|
|
|
|
|
|
|
// ???
|
|
|
this.springing = 0.0009; |
|
|
this.springing = 0.0009; |
|
|
|
|
|
|
|
|
|
|
|
// ???
|
|
|
this.damping = 0.98; |
|
|
this.damping = 0.98; |
|
|
|
|
|
|
|
|
|
|
|
// Value that controls the tightness or looseness of the curves between the
|
|
|
|
|
|
// x,y values of the shape. AFAIK, this value can go between -5 and +5.
|
|
|
|
|
|
// With +5 we have very sharp curves and edges.
|
|
|
this.organicConstant = random(-5, 5); |
|
|
this.organicConstant = random(-5, 5); |
|
|
|
|
|
|
|
|
|
|
|
// The x,y values which determine where the shape is currently. These are
|
|
|
|
|
|
// required in order to calculate where the shape is currently so that we
|
|
|
|
|
|
// can then go about moving it to the new destination.
|
|
|
this.startXs = []; |
|
|
this.startXs = []; |
|
|
this.startYs = []; |
|
|
this.startYs = []; |
|
|
|
|
|
|
|
|
|
|
|
// The x,y values which track the new position of the shape (and therefore
|
|
|
|
|
|
// update the `startXs` and `startYs`) as the shape moves about the
|
|
|
|
|
|
// environment
|
|
|
this.xs = []; |
|
|
this.xs = []; |
|
|
this.ys = []; |
|
|
this.ys = []; |
|
|
|
|
|
|
|
|
|
|
|
// ???
|
|
|
this.angles = []; |
|
|
this.angles = []; |
|
|
this.frequency = []; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// ???
|
|
|
|
|
|
this.frequencies = []; |
|
|
|
|
|
|
|
|
|
|
|
// Random x,y values (only randomly chosen once, then fixed) which are used
|
|
|
|
|
|
// in the calculation of the curve drawing between the x,y vectors of the
|
|
|
|
|
|
// shape
|
|
|
this.randXs = []; |
|
|
this.randXs = []; |
|
|
this.randYs = []; |
|
|
this.randYs = []; |
|
|
|
|
|
|
|
|
|
|
|
// Number of edges of the shape
|
|
|
this.edges = random(3, 10); |
|
|
this.edges = random(3, 10); |
|
|
|
|
|
|
|
|
|
|
|
// ???
|
|
|
this.radius = random(120, 140); |
|
|
this.radius = random(120, 140); |
|
|
this.angle = radians(360 / this.edges); |
|
|
|
|
|
|
|
|
|
|
|
this.destX = random(canvasWidth); |
|
|
// ???
|
|
|
this.destY = random(canvasHeight); |
|
|
this.angle = radians(360 / this.edges); |
|
|
|
|
|
|
|
|
|
|
|
// ???
|
|
|
this.centerX = random(windowWidth); |
|
|
this.centerX = random(windowWidth); |
|
|
this.centerY = random(windowHeight); |
|
|
this.centerY = random(windowHeight); |
|
|
|
|
|
|
|
@ -182,7 +242,7 @@ class GeneratedShape { |
|
|
|
|
|
|
|
|
initialise() { |
|
|
initialise() { |
|
|
/** |
|
|
/** |
|
|
* Initialise the shape. |
|
|
* Initialise the shape values. |
|
|
**/ |
|
|
**/ |
|
|
for (let i = 0; i < this.edges; i++) { |
|
|
for (let i = 0; i < this.edges; i++) { |
|
|
this.startXs[i] = 0; |
|
|
this.startXs[i] = 0; |
|
@ -195,7 +255,7 @@ class GeneratedShape { |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
for (let i = 0; i < this.edges; i++) { |
|
|
for (let i = 0; i < this.edges; i++) { |
|
|
this.frequency[i] = random(5, 12); |
|
|
this.frequencies[i] = random(5, 12); |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
@ -203,34 +263,40 @@ class GeneratedShape { |
|
|
/** |
|
|
/** |
|
|
* Detect if the shape collides with another shape. |
|
|
* Detect if the shape collides with another shape. |
|
|
**/ |
|
|
**/ |
|
|
// TODO: implement once again
|
|
|
return false; // TODO: implement once again
|
|
|
return false; |
|
|
|
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
sound() { |
|
|
sound() { |
|
|
/** |
|
|
/** |
|
|
* Play a sound after a collision is detected. |
|
|
* Play a sound after a collision is detected. |
|
|
**/ |
|
|
**/ |
|
|
// TODO: implement once again
|
|
|
return; // TODO: implement once again
|
|
|
return; |
|
|
|
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
fadein() { |
|
|
docolour() { |
|
|
/** |
|
|
/** |
|
|
* Fade-in the shape using alpha values. |
|
|
* Draw colour and fade-in shape. |
|
|
**/ |
|
|
**/ |
|
|
if (this.opacity < 256) { |
|
|
if (this.opacity != 256) { |
|
|
let currentAlpha = this.colour._getAlpha(); |
|
|
if (this.opacity < 256) { |
|
|
this.colour.setAlpha(currentAlpha + random(0, 3)); |
|
|
// shape should fade in, so increment alpha value
|
|
|
} else { |
|
|
let currentAlpha = this.colour._getAlpha(); |
|
|
this.opacity = 256; |
|
|
this.colour.setAlpha(currentAlpha + random(0, 3)); |
|
|
|
|
|
} else { |
|
|
|
|
|
// shape has faded-in, show it as fully visible now
|
|
|
|
|
|
this.opacity = 256; |
|
|
|
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
fill(this.colour); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
chooseColour() { |
|
|
chooseColour() { |
|
|
/** |
|
|
/** |
|
|
* Choose a colour for the shape. |
|
|
* Choose a colour for the shape. |
|
|
**/ |
|
|
**/ |
|
|
|
|
|
// TODO: choose nicer colours
|
|
|
|
|
|
// TODO: Can we have gradient colours
|
|
|
|
|
|
// TODO: Can we have multiple colours
|
|
|
let colourChoices = [ |
|
|
let colourChoices = [ |
|
|
color("red"), |
|
|
color("red"), |
|
|
color("blue"), |
|
|
color("blue"), |
|
@ -238,19 +304,35 @@ class GeneratedShape { |
|
|
color("black"), |
|
|
color("black"), |
|
|
color("white") |
|
|
color("white") |
|
|
]; |
|
|
]; |
|
|
|
|
|
|
|
|
let index = floor(random(0, colourChoices.length)); |
|
|
let index = floor(random(0, colourChoices.length)); |
|
|
let chosenColour = colourChoices[index]; |
|
|
let chosenColour = colourChoices[index]; |
|
|
|
|
|
|
|
|
|
|
|
// set shape opacity to 0 initially to enable fade-in
|
|
|
chosenColour.setAlpha(this.opacity); |
|
|
chosenColour.setAlpha(this.opacity); |
|
|
|
|
|
|
|
|
return chosenColour; |
|
|
return chosenColour; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
curve() { |
|
|
|
|
|
/** |
|
|
|
|
|
* Curve the shape. |
|
|
|
|
|
**/ |
|
|
|
|
|
curveTightness(this.organicConstant); |
|
|
|
|
|
beginShape(); |
|
|
|
|
|
for (let i = 0; i < this.edges; i++) { |
|
|
|
|
|
curveVertex(this.xs[i], this.ys[i]); |
|
|
|
|
|
} |
|
|
|
|
|
endShape(CLOSE); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
draw() { |
|
|
draw() { |
|
|
/** |
|
|
/** |
|
|
* Draw the shape vectors. |
|
|
* Draw the shape vectors. |
|
|
**/ |
|
|
**/ |
|
|
if (this.opacity != 256) this.fadein(); |
|
|
this.docolour(); |
|
|
fill(this.colour); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// set the start x,y positions for the shape on each draw loop
|
|
|
for (let i = 0; i < this.edges; i++) { |
|
|
for (let i = 0; i < this.edges; i++) { |
|
|
this.startXs[i] = |
|
|
this.startXs[i] = |
|
|
this.centerX + cos(this.angle * i) * this.radius + this.randXs[i]; |
|
|
this.centerX + cos(this.angle * i) * this.radius + this.randXs[i]; |
|
@ -258,12 +340,7 @@ class GeneratedShape { |
|
|
this.centerY + sin(this.angle * i) * this.radius + this.randYs[i]; |
|
|
this.centerY + sin(this.angle * i) * this.radius + this.randYs[i]; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
curveTightness(this.organicConstant); |
|
|
this.curve(); |
|
|
beginShape(); |
|
|
|
|
|
for (let i = 0; i < this.edges; i++) { |
|
|
|
|
|
curveVertex(this.xs[i], this.ys[i]); |
|
|
|
|
|
} |
|
|
|
|
|
endShape(CLOSE); |
|
|
|
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
move() { |
|
|
move() { |
|
@ -292,39 +369,31 @@ class GeneratedShape { |
|
|
this.startXs[i] + sin(radians(this.angles[i])) * (this.accelX * 2); |
|
|
this.startXs[i] + sin(radians(this.angles[i])) * (this.accelX * 2); |
|
|
this.ys[i] = |
|
|
this.ys[i] = |
|
|
this.startYs[i] + sin(radians(this.angles[i])) * (this.accelY * 2); |
|
|
this.startYs[i] + sin(radians(this.angles[i])) * (this.accelY * 2); |
|
|
this.angles[i] += this.frequency[i]; |
|
|
this.angles[i] += this.frequencies[i]; |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
function setup() { |
|
|
function setup() { |
|
|
/** |
|
|
/** |
|
|
* The p5.js initial setup function. |
|
|
* The initial setup function called once on start. |
|
|
**/ |
|
|
**/ |
|
|
canvasWidth = windowWidth; |
|
|
createCanvas(windowWidth, windowHeight); |
|
|
canvasHeight = windowHeight; |
|
|
|
|
|
|
|
|
|
|
|
createCanvas(canvasWidth, canvasHeight); |
|
|
|
|
|
frameRate(frameRate); |
|
|
frameRate(frameRate); |
|
|
|
|
|
|
|
|
setupRecording(); |
|
|
setupRecording(); |
|
|
|
|
|
|
|
|
centerX = canvasWidth / 2; |
|
|
|
|
|
centerY = canvasHeight / 2; |
|
|
|
|
|
|
|
|
|
|
|
screenX = toScreenX = 0; |
|
|
|
|
|
screenY = toScreenY = 0; |
|
|
|
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
function draw() { |
|
|
function draw() { |
|
|
/** |
|
|
/** |
|
|
* The p5.js draw loop. |
|
|
* The draw loop which is called x times a second where x is the frameRate. |
|
|
**/ |
|
|
**/ |
|
|
background("#69D2E7"); |
|
|
background("#69D2E7"); |
|
|
blendMode(BLEND); |
|
|
blendMode(BLEND); |
|
|
smooth(); |
|
|
smooth(); |
|
|
noStroke(); |
|
|
noStroke(); |
|
|
|
|
|
|
|
|
|
|
|
// offset the window view based on new values of x,y related to the screen.
|
|
|
|
|
|
// These values are generated once the user drags the screen with the mouse.
|
|
|
screenX = lerp(screenX, toScreenX, 0.2); |
|
|
screenX = lerp(screenX, toScreenX, 0.2); |
|
|
screenY = lerp(screenY, toScreenY, 0.2); |
|
|
screenY = lerp(screenY, toScreenY, 0.2); |
|
|
translate(screenX, screenY); |
|
|
translate(screenX, screenY); |
|
@ -334,30 +403,14 @@ function draw() { |
|
|
newSoundJustRecorded = false; |
|
|
newSoundJustRecorded = false; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
if (millis() >= 1000 + timer) { |
|
|
|
|
|
secondTick = true; |
|
|
|
|
|
timer = millis(); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
for (let i = 0; i < shapes.length; i++) { |
|
|
for (let i = 0; i < shapes.length; i++) { |
|
|
let shape = shapes[i]; |
|
|
let shape = shapes[i]; |
|
|
|
|
|
|
|
|
shape.draw(); |
|
|
shape.draw(); |
|
|
shape.move(); |
|
|
shape.move(); |
|
|
|
|
|
|
|
|
if (secondTick) { |
|
|
|
|
|
setTimeout(function() { |
|
|
|
|
|
shape.destX = random(canvasWidth); |
|
|
|
|
|
shape.destY = random(canvasHeight); |
|
|
|
|
|
}, random(100, 3000)); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if (shape.collide(shapes) === true) { |
|
|
if (shape.collide(shapes) === true) { |
|
|
shape.sound(); |
|
|
shape.sound(); |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
secondTick = false; |
|
|
|
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
function mouseDragged() { |
|
|
function mouseDragged() { |
|
|