Lucid Projects

Blog - Day to day mumblings...
P5js Sound Collaboration

P5js Sound Collaboration

20 Nov 2019 - Jake Sherwood

Exploring music collaboration with p5js 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.

categories: icm

join me on this crazy ride. enter your email:

contact [at] jakesherwood [dot] com

contact me if you'd like to work together