P5js Sound Collaboration
20 Nov 2019 - Jake Sherwood
Exploring music collaboration with p5js
Exploring music collaboration with p5js
The assignment was to manipulate sound with p5js with a partner and present to the class.
I thought it would be cool to figure out a way to collaborate live with my partner for our presentation.
This meant I needed to figure out how to get 2 sketches to talk to each other.
Following Dan Shiffmans’s tutorial here I was able to get a node.js server set up and have two instances of a p5 sketch talk to each other over a local network.
This allowed Na and I to collaboratively sample audio tracks and manipulate them in realtime.
I have a serve_sound.js file that acts as my node server.
It is listening from transmitions from one instance of the sketch and stores them to a local directory. It then notifies the other instance that there is a new sample available.
server_sound.js code:
const express = require('express'); //make express available
const app = express(); //invoke express
const multer = require('multer') //use multer to upload blob data
const upload = multer(); // set multer to be the upload variable (just like express, see above ( include it, then use it/set it up))
const fs = require('fs'); //use the file system so we can save files
app.use(express.static('public'));
app.post('/upload', upload.single('soundBlob'), function (req, res, next) {
console.log(req.file); // see what got uploaded
let uploadLocation = __dirname + '/public/uploads/' + req.file.originalname // where to save the file to. make sure the incoming name has a .wav extension
fs.writeFileSync(uploadLocation, Buffer.from(new Uint8Array(req.file.buffer))); // write the blob to the server as a file
res.sendStatus(200); //send back that everything went ok
})
//makes the app listen for requests on port 3000
let server = app.listen(3000, function(){
console.log("app listening on port 3000!")
})
console.log("my socket server is running");
let socket = require('socket.io');
let io = socket(server);
io.sockets.on('connection', newConnection);
function newConnection(socket){
console.log('new connection' + socket.id);
//console.log(socket.id);
//socket.on('mouse', mouseMsg);
socket.on('oneSound', simpleSound);
function simpleSound(myName){
socket.broadcast.emit('oneSound', myName);
console.log("simpleSound ran");
}
}
The sketch_sound_new.js file controls the recording, playback and looping. We were able to run 2 instances of the same sketch on a local network and have them communicate via socket.io
sketch_sound_new.js:
let oscs = [];
let mic;
let recorder;
let loopSound;
let loops = [];
let serverSound;
let loopCounter = 0;
let soundBlob;
let myName; // used for naming soundfiles on server
let startName = 0; // starting soundfile name
let state = 0; // state of recording mic sample
let doorSound;
let beatTrack;
let piano;
let sax;
let notes = [1, 1.125, 1.25, 1.334, 1.5, 1.667, 1.875, 2];
let socket;
let rectColor = 'green';
let loopText = 'Press L to record loop sample from mic.';
let formdata; //create a from to of data to upload to the server
function preload() {
// doorSound = loadSound('doorchime.mp3');
beatTrack = loadSound('drums.wav');
piano = loadSound('piano3.wav');
sax = loadSound('sax.wav');
}
function setup() {
createCanvas(400, 400);
// set up connection to local host server
//socket = io.connect('http://10.18.196.9:3000/'); // need to up date IP to share with another user on same network - or localhost:3000
socket = io.connect('localhost:3000/'); // need to up date IP to share with another user on same network - - or localhost:3000
//socket = io.connect('http://192.168.86.245:3000'); //jakes home network IP
// set up receiver from local host server
//socket.on('mouse', newDrawing);
socket.on('oneSound', instanceSound);
// Set up mic
mic = new p5.AudioIn();
// Set up recorder
recorder = new p5.SoundRecorder();
// Connect mic to recorder
recorder.setInput(mic);
// Turn on mic
mic.start();
}
//FUNCTIONS FOR CLIENT INSTANCES TO RUN
function instanceSound(myName) {
//play sound here when oneSound broadcast is received
sax.play();
console.log("new loop received");
console.log(serverSound);
loops.push(loadSound('uploads/' + myName + '.mp3'));
}
//END FUNCTIONS FOR CLIENT INSTANCES TO RUN
function draw() {
background(220);
// Display instructions
textAlign(CENTER, CENTER);
fill(rectColor);
rectMode(CENTER);
rect(width / 2 - 140, 70, 20, 20);
fill(0);
let ins = [loopText, 'Press Z to stop and remove last loop sample', 'Press B to start or pause the beat', 'Press P to play or stop piano' ,'Press S to send loop & play sax','Press O to change sample rate', 'Press D to add delay to sample', 'Press R to add reverb to sample'];
for (let i = 0; i < ins.length; i++) {
textFont('Verdana');
text(ins[i], width / 2, i * 30 + 70)
}
}
function keyPressed() {
//fix Failed to execute 'createBuffer' on 'BaseAudioContext error
getAudioContext().resume();
if (key === 'p') {
// let f = random(notes);
let f = notes[2];
piano.rate(f);
// piano.loop();
if (piano.isPlaying()) {
piano.stop();
} else {
piano.loop();
}
console.log('p pressed - start piano');
}
if (key === 'o') {
// change rate of last mic sample
let speed = map(mouseY, 0.1, height, 0, 2);
speed = constrain(speed, 0.01, 4);
// let oscSample = loops[loopCounter];
serverSound.rate(speed);
}
if (key === 'd'){
delay = new p5.Delay();
serverSound.setVolume(0.1);
delay.process(serverSound, .12, .7, 2300);
}
if (key === 'r'){
reverb = new p5.Reverb();
reverb.process(serverSound, 3,2);
}
if (key === 'l') {
console.log("l pressed");
// Increment the state on every keypress
state++;
// There are only 3 states
state %= 3;
// Record the sample in state 1
if (state == 1) {
loopSound = new p5.SoundFile();
recorder.record(loopSound);
loopText = 'Press L to stop recording loop sample';
rectColor = 'red';
// Stop recording in state 2
} else if (state == 2) {
recorder.stop();
loopText = 'Press L to play recorded loop sample';
rectColor = 'gray';
console.log(loopSound);
soundBlob = loopSound.getBlob();
console.log(soundBlob);
// Loop the sample in state 3
} else {
loopSound.loop();
loopSound.setVolume(1);
loopText = 'Press L key to record loop sample';
rectColor = 'green';
}
}
if (key === 'z') {
console.log('z pressed - stop or replay loop');
let lastLoop = loopCounter - 1;
loops[lastLoop].stop();
loops.pop();
loopCounter--;
console.log('stopped loop' + lastLoop);
}
if (key === 'n') {
console.log('n pressed - play new loop');
console.log(loops);
serverSound = loops[loopCounter];
serverSound.loop();
console.log("I'm playing loops " + loopCounter)
serverSound.setVolume(0.15);
loopCounter++;
}
// start beat track
if (key === 'b') {
if (beatTrack.isPlaying()) {
beatTrack.stop();
} else {
beatTrack.loop();
}
console.log('b pressed - start beat');
beatTrack.setVolume(1);
}
// stop beat track
if (key === 'x') {
console.log('x pressed - stop beat');
beatTrack.stop();
}
// send loop
if (key === 's') {
console.log('s pressed - send loop');
sendSoundBlob();
socket.emit('oneSound', myName); // name is important
console.log("sent loop");
}
return false; // prevent default
}
function sendSoundBlob() {
if (startName == 0) {
myName = startName;
}
console.log("startName " + startName);
myName = startName;
//need to create new formdata object everytime
formdata = new FormData(); //create a form of data to upload to the server
formdata.append('soundBlob', soundBlob, myName + '.mp3'); // append the sound blob and the name of the file. third argument will show up on the server as req.file.originalname
// Now we can send the blob to a server...
var serverUrl = '/upload'; //we've made a POST endpoint on the server at /upload
//build a HTTP POST request
var httpRequestOptions = {
method: 'POST',
body: formdata, // with our form data packaged above
headers: new Headers({
'enctype': 'multipart/form-data' // the enctype is important to work with multer on the server
})
};
// console.log(httpRequestOptions);
// console.log(formdata);
// use p5 to make the POST request at our URL and with our options
httpDo(
serverUrl,
httpRequestOptions,
(successStatusCode) => { //if we were successful...
console.log("uploaded recording successfully: " + successStatusCode)
},
(error) => {
console.error(error);
}
)
startName++;
}
Overall I had a lot of fun with this assignment. If had it to do over I would add a few more features and a way of manipulating the tracks individually instead of only the last one.