Search code examples
p5.jsmidiweb-midimidi-instrument

Integrating Webmidi.js and p5.js


I am wondering how to integrate integrate Webmidi.js, p5.js, and my Akai mPK49 midi controller? This is for a final project for school.

Reading through the Webmidi.js Basics, I was able to get the program to "listed" to my midi controller. Even though the p5 website was throwing a "TypeError: Cannot read properties of undefined (reading 'channels')" error, the note names were printing in the console. I then tried to see if I could play midi notes in my DAW Logic Pro X. I noticed that both logic and the P5.js/Webmidi.js program were both acknowledging that I was playing keys on the controller.

I went on to the next step to add a basic synth and to try to play an actual note. The console stopped showing that I was playing notes and only showed the Akai ports and that Logic Pro was an input and output.

Additionally, I commented out new lines of code for the synth and the console still would not show the midi notes in the console. So I am a bit stumped. Here is a link to the P5.js sketch. Any help is appreciated!

  function setup() {
  createCanvas(400, 400);
  WebMidi
  .enable()
  //.then(() => console.log("WebMidi enabled!"))
  .then(onEnabled)
  .catch(err => alert(err));
  
}

function draw() {
  background(220);
 
}
function onEnabled() {

  // Inputs
  WebMidi.inputs.forEach(input => console.log(input.manufacturer, input.name));
  
  // Outputs
  WebMidi.outputs.forEach(output => console.log(output.manufacturer, output.name));

   const myInput = WebMidi.getInputByName("Akai MPK 49");
   const mySynth = myInput.channels[1]; // <-- the MIDI channel (10)
  
  myInput.addListener("noteon", e => {
  console.log(e.note.identifier);
    })
  
  mySynth.addListener("noteon", e => {
  console.log(e.note.identifier, e.message.channel);
})
  
  let output = WebMidi.outputs[0];
let channel = output.channels[1];
channel.playNote("C3");
}

Solution

  • The following works on my system. p5.TriOsc() is used to play the notes with this technique. You will need to change the name for your device; you don't need the manufacturer, only the device name. You will also need to add the following script to index.html:

    <script src="https://cdn.jsdelivr.net/npm/webmidi@latest/dist/iife/webmidi.iife.js"></script>
    
    function setup() {
      createCanvas(200, 200);
      osc = new p5.TriOsc(); // Need this to play the notes
      env = new p5.Envelope();
      WebMidi.enable()
        .then(() => console.log("WebMidi enabled!"))
        .then(onEnabled)
        .catch((err) => alert(err));
    }
    
    function draw() {
      background(220);
    }
    
    function playSound(midiVal) {
      osc.start();
      freq = midiToFreq(midiVal);
      osc.freq(freq);
      env.ramp(osc, 0, 1.0, 0);
    }
    
    function onEnabled() {
      // Inputs
      WebMidi.inputs.forEach((input) =>
        console.log(input.manufacturer, input.name)
      );
    
      // Outputs
      WebMidi.outputs.forEach((output) =>
        console.log(output.manufacturer, output.name)
      );
    
      const myInput = WebMidi.getInputByName("Q Mini");
    
      myInput.addListener("noteon", (e) => {
        console.log(e.note.identifier);
        console.log("value =", e.message.data[1]);
        playSound(e.message.data[1]);
      });
    }
    
    

    Alternate Method:

    The following is an alternate method which does not require any additional code to be added to index.html or a 'listener' to be requested. Its shortcoming is that onmidimessage is reported twice and therefore the note is played twice. To work around this issue a counter is added and reset to zero after the first iteration, thereby playing the note only once.

    let counter = 0;
    
    function setup() {
      createCanvas(100, 100);
      osc = new p5.TriOsc(); // Need this to play the notes
      env = new p5.Envelope();
    }
    
    function playSound(midiVal) {
      osc.start();
      freq = midiToFreq(midiVal);
      osc.freq(freq);
      env.ramp(osc, 0, 1.0, 0);
    }
    
    navigator.requestMIDIAccess().then((midiAccess) => {
      console.log(midiAccess.inputs);
      Array.from(midiAccess.inputs).forEach((input) => {
        input[1].onmidimessage = (msg) => {
          if (counter == 0) {
            console.log("value =", msg.data[1]);
            playSound(msg.data[1]);
          }
          counter++;
          if (counter > 1) counter = 0;
        };
      });
    });
    
    function draw() {
      background(220);
    }