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.
VIDEO