Bob McBobson
Bob McBobson

Reputation: 914

How to import and utilize P5.Sound in Vue?

I have been trying to make a music visualizer app using Vue and P5, and after tinkering with P5 using this article as my guide (https://medium.com/js-dojo/experiment-with-p5-js-on-vue-7ebc05030d33), I managed to get a Canvas rendered with some cool looking graphics.

Now, I am trying to create a link between the waveform/amplitude of a given song and the visuals rendered in the canvas. I have been trying to get the constructors/functions from the P5.sound library to load a song from a file path, and then use the output from a FFT object to control the visuals rendering in the canvas.

Now, my research has indicated that the P5 library must be run in instance mode in order to function (https://github.com/processing/p5.js/wiki/Global-and-instance-mode), and I have done my very best to adhere to this approach in my Vue project. But although the visual rendering works, none of the P5.sound functionalities do.

Here is the code to my model which sets up the P5 objects:

import P5 from 'p5';
import P5sound from "p5/lib/addons/p5.sound";


let p5;
let fft;
let sound;

export function main(_p5) {
  p5 = _p5;

  p5.setup = () => {
    p5.createCanvas(500, 500);
    p5.background(100);
    fft = new p5.FFT();
    fft.setInput("../assets/sawtooth.mp3")
    sound.amp(0.2);
  };
  p5.draw = () => {
    p5.background(220);
    let spectrum = fft.analyze();
    noStroke();
    fill(255, 0, 255);
    for (let i = 0; i < spectrum.length; i++) {
      let x = map(i, 0, spectrum.length, 0, width);
      let h = -height + map(spectrum[i], 0, 255, height, 0);
      rect(x, height, width / spectrum.length, h);
    }

    let waveform = fft.waveform();
    noFill();
    beginShape();
    stroke(20);
    for (let i = 0; i < waveform.length; i++){
      let x = map(i, 0, waveform.length, 0, width);
      let y = map( waveform[i], -1, 1, 0, height);
      vertex(x,y);
    }
    endShape();
  };
}

Then, in my MusicVisualization.vue component, I call this model in the mounted() section of the component:

import P5 from 'p5';
var musicVisualizerModel = require("../models/musicVisualizerModel.js");

export default {
  name: "MusicVisualization",
  data(){
      return{
          message: ""
      } 
  },
  mounted() {
    new P5(musicVisualizerModel.main);
  }
};

No matter how I try to import my P5.sound library, the line fft = new p5.FFT() always yields an error p5.FFT is not a constructor. I have checked the P5.js module in my node_modules, and I can clearly see the P5.sound.js library present in the p5/lib/addons/p5.sound file path, but I cannot utilize any of its functionality. I should note that when I remove all P5.sound functionality from the model's code, and only have a canvas with some simple shapes, it works fine.

I installed P5 using npm install --save p5, and have tried numerous approaches to getting P5.sound to work, such as the P5-manager package (https://www.npmjs.com/package/p5-manager/v/0.1.0) and the Vue-P5 wrapper (https://www.npmjs.com/package/vue-p5). I have also tried implementing some of the proposed solutions given in this similar question (Using p5.sound.js in instance mode: 'p5.Amplitude() not a constructor'). None of these work, although I may be implementing incorrectly. Does anybody know how I can import the P5.sound library into my Vue project?

UPDATES

Another error that I noticed if I did not run the fft= new p5.FFT() line was the error ReferenceError: p5 is not defined. This error occurred whenever I tried to import the P5.sound library (with either import P5sound from "p5/lib/addons/p5.sound"; or with import "p5/lib/addons/p5.sound";). I looked deeper into this problem and found others had experienced it as well (https://github.com/processing/p5.js-sound/issues/453). Apparently, one user resolved the issue by "downgrading to 0.9, copying the sound.js into project folder, boosted back into 1.0 and just linked imports to the local, 0.9 version of sound." Sadly, I am unsure how to carry out this solution. Would anybody know how?

Upvotes: 2

Views: 3571

Answers (4)

XinYue WR
XinYue WR

Reputation: 1

I utilized ViteSSG (Static Site Generator for Vite) to set up my Vue app and registered p5.js globally in the main entry file, main.ts.

Main Entry File: main.ts

import { ViteSSG } from 'vite-ssg'
import App from './App.vue'
import { setupLayouts } from 'virtual:generated-layouts'
import { routes } from 'vue-router/auto/routes'
import p5 from 'p5'

import 'uno.css'
import 'animate.css'
import './style.css'
import 'splitting/dist/splitting.css'
import 'splitting/dist/splitting-cells.css'

// Register p5 as a global variable
window.p5 = p5

export const createApp = ViteSSG(
  App,
  {
    routes: setupLayouts(routes),
    base: import.meta.env.BASE_URL,
  },
  ({ app, router, routes, isClient, initialState }) => {},
)

This way, p5 is set as a global variable, accessible in any Vue component.

Vue Component Example In a Vue component, I was able to directly use p5 for loading and manipulating an audio file.

<template>
  <div ref="canvasWrapper"></div>
</template>

<script setup>
import '~/models/p5.sound'

let song

const preload = (p) => {
  console.log(p)
  song = p.loadSound('The_Deep.mp3')
}

onMounted(() => {
  new p5(preload)
})
</script>

I consider this implementation is not elegant, but it temporarily solves my current problem. I hope p5.js will have better ES6 support in the future.

Upvotes: 0

nuc
nuc

Reputation: 371

The problem is that p5-sound relies on p5 globally available.

If you are using webpack, that can be solved easily by using webpack's ProvidePlugin, like so:

  plugins: [
    new webpack.ProvidePlugin({
      p5: 'p5',
    })
  ]

Then you should be able to import via:

import p5 from 'p5';
import 'p5/lib/addons/p5.sound';

Upvotes: 2

Adam C
Adam C

Reputation: 11

I had a similar problem using p5 in react, but I was able to get this working (for the time being 😅) by including the p5 scripts in my html file.

<script src="https://cdn.jsdelivr.net/npm/[email protected]/lib/p5.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/lib/addons/p5.sound.min.js"></script>

And then when creating my p5 instance in React I call:

new window.p5(sketch, node);

My sketch file looks something like:

const sketch = (p, props) => {
  p.preload = () => {
    ...
  };

  p.setup = function () {
    ...
  };

  p.draw = () => {
    ...
  };
};

export default sketch;

In my sketch logic, I use the instance p variable for the standard p5.js functionality like drawing shapes. Whenever I need to use a constructor of a p5 class like AudioIn, Amplitude, SoundFile, etc., I create it using the global p5 variable. For example:

new window.p5.AudioIn()

I believe this is necessary because the p5 sound addon library adds functions and classes onto the global p5 variable. When I was using es6 imports, there must have been some mess where the sound addon was not being added correctly to the imported p5 class.

One day there will hopefully be a better es6 module system for p5.js, but this seems to do the trick for me.

Upvotes: 1

Bob McBobson
Bob McBobson

Reputation: 914

Thanks to help I received on the Github issues page (https://github.com/processing/p5.js-sound/issues/453), I figured out how to get the P5.sound library to import.

First, I uninstalled the P5.js in the node_modules, then installed P5.js version 0.9.0:

npm uninstall p5
npm install [email protected]

Next, after locating the P5 module in node_modules, I copied the P5 sound lib file and pasted it into my project's src directory, and then replaced the import P5sound from "p5/lib/addons/p5.sound" with import "../p5.sound.js" (which represented my relative file path). Then I installed the latest version of P5.js in the terminal with the following command:

npm install p5@1

And now I can import the sound library without getting the errors ReferenceError: p5 is not defined or p5.FFT is not a constructor.

Upvotes: 5

Related Questions