Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Play Audio-Visualizer #851

Closed
wants to merge 10 commits into from
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
"jspdf": "^2.5.1",
"lodash": "^4.17.21",
"node-sass": "^8.0.0",
"p5": "^1.5.0",
"p5": "^0.9.0",
"react": "^18.0.0",
"react-codemirror2": "^7.2.1",
"react-confetti": "^6.1.0",
Expand Down
19 changes: 19 additions & 0 deletions src/plays/audio-visualizer/AudioVisualizer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import React from "react";
import PlayHeader from "common/playlists/PlayHeader";
import Canvas from "./Canvas/Canvas";
import "./styles.css";

const AudioVisualizer = (props: React.ReactNode) => {
return (
<>
<div className="play-details">
<PlayHeader play={props} />
<div className="play-details-body">
<Canvas />
</div>
</div>
</>
);
};

export default AudioVisualizer;
181 changes: 181 additions & 0 deletions src/plays/audio-visualizer/Canvas/Canvas.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
// Disclaimer
// "everglow.mp3" by Patrick Patrikios is a being used under Creative Commons License.
// Original Source it was taken from https://youtu.be/pbq6sKq4vUk
// The Song is not our Trademark.

import React, { useEffect, useRef } from "react";
import Sketch from "react-p5";

import p5 from "p5";
import "p5/lib/addons/p5.sound.js";

import { Mike, Loop, SongInput } from "../components/exports";

const Canvas = () => {
const [useMic, setUseMic] = React.useState<boolean>(false);
let [mic, setMic] = React.useState<p5.AudioIn | undefined>();
let [song, setSong] = React.useState<p5.MediaElement | undefined>();
let [fft, setFFT] = React.useState<p5.FFT | undefined>();
const [loop, setLoop] = React.useState<boolean>(true);
const [songPath, setSongPath] = React.useState<string>("");
const [playingSong, setSetPlayingSong] = React.useState<string>(
"https://res.cloudinary.com/dig6ih2ni/video/upload/v1671792121/Audio-Visualizer/everglow_hykjng.mp3"
);

// Preloading the song before canvas is rendered
function preload(p: p5) {
song = p.createAudio(playingSong);
fft = new p5.FFT(0.8, 1024);
song?.autoplay(true);
setSong(song);
setFFT(fft);
}

// Setting up the canvas
function setup(p: p5, canvasParentRef: Element) {
p.createCanvas(window.innerWidth, window.innerHeight / 1.5).parent(
canvasParentRef
);

// Pausing the song on click if it is playing
canvasParentRef.addEventListener("click", () => {
if (!song) return;
if (p.isLooping()) {
song.pause();
p.noLoop();
} else {
loop ? song.loop() : song.play();
p.loop();
setFFT(fft);
setSong(song);
}
return;
});

fft?.setInput(song);
}
function draw(p: p5) {
p.background("#1c1c1c");
p.fill(255);
p.rectMode(p.CENTER);
if (!fft) return;

let spectrum = fft.analyze();

for (let i = 0; i < p.width; i++) {
let x = p.map(i, 0, p.width / 2, 0, p.width);
let h = p.constrain(
p.map(spectrum[i], 0, 255, 0, p.height),
0,
p.height
);
p.stroke(`hsl(${spectrum[i]}, 100%, 50%)`);
p.rect(x, p.height / 2, p.width / spectrum.length, h);
}
}
// Updating canvas size on resize
function windowResized(p: p5) {
p.resizeCanvas(window.innerWidth, window.innerHeight);
}

// Enabling Fullscreen Mode
function doubleClicked(p: p5) {
if (p.deviceOrientation === "portrait") return;
if (!window.screenTop && !window.screenY) {
p.fullscreen(true);
} else {
p.fullscreen(false);
}
}

function touchStarted(p: p5) {
if (!p.isLooping() && song.time() <= 0) {
alert("Please enable sound to play the audio file");
}
}
const toggleMic = () => {
setUseMic(!useMic);
};
const toggleLoop = () => {
setLoop(!loop);
if (!loop) {
song?.loop();
} else {
song?.noLoop();
}
};
// Using mic instead when the user clicks on the mic button
useEffect(() => {
if (!song || !fft) return;
if (useMic) {
mic = new p5.AudioIn();
mic.start();
song.pause();
fft.setInput(mic);
setMic(mic);
setFFT(fft);
setSong(song);
} else if (!useMic && mic) {
mic.stop();
loop ? song.loop() : song.play();
fft.setInput(song);
setMic(mic);
setFFT(fft);
setSong(song);
}
}, [useMic]);

// Updating the song when the user uploads/enter new url for the song
const updateSongFile = (isFile?: boolean, url?: string) => {
if (!song || !fft) return;
if (isFile && url) {
song.src = url;
setSetPlayingSong(url);
song.play();
fft.setInput(song);
setFFT(fft);
setSong(song);
setSongPath("");
return;
}
if (!isFile || !songPath) return;
if (!songPath.match(/^(http|https):\/\/[^ "]+$/)) return;
setSetPlayingSong(songPath);
song.src = songPath;
song.play();
fft.setInput(song);
setFFT(fft);
setSong(song);
setSongPath("");
};
// Setting the song path when the user enters the url
const updateSongPath = (value: string) => {
setSongPath(value);
};

return (
<>
<div className="features-container">
<Mike toggleMic={toggleMic} useMic={useMic} />
<SongInput
updateSongFile={updateSongFile}
updateSongPath={updateSongPath}
songPath={songPath}
/>
<Loop loop={loop} toggleLoop={toggleLoop} useMic={useMic} />
</div>

<Sketch
className="canvas"
preload={preload}
setup={setup}
draw={draw}
touchStarted={touchStarted}
windowResized={windowResized}
doubleClicked={doubleClicked}
/>
</>
);
};

export default Canvas;
27 changes: 27 additions & 0 deletions src/plays/audio-visualizer/Readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Audio Visualizer

A straightforward audio visualizer that depicts sounds using wave spectrum .

## Play Demographic

- Language: ts
- Level: Intermediate

## Creator Information

- User: DyingintheDarkness
- Gihub Link: https://github.com/DyingintheDarkness
- Blog:
- Video:

## Implementation Details

Update your implementation idea and details here

## Consideration

Update all considerations(if any)

## Resources

Update external resources(if any)
34 changes: 34 additions & 0 deletions src/plays/audio-visualizer/components/Loop/Loop.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import React from "react";

type LoopProps = {
loop: boolean;
toggleLoop: () => void;
useMic: boolean;
};

const Loop = ({ loop, toggleLoop, useMic }: LoopProps) => {
return (
<span
onClick={toggleLoop}
className={loop ? "loop svg-circle" : "svg-circle"}
style={{ opacity: useMic ? 0.5 : 1 }}
>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
strokeWidth={1.5}
stroke="currentColor"
className="aud-svg"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M19.5 12c0-1.232-.046-2.453-.138-3.662a4.006 4.006 0 00-3.7-3.7 48.678 48.678 0 00-7.324 0 4.006 4.006 0 00-3.7 3.7c-.017.22-.032.441-.046.662M19.5 12l3-3m-3 3l-3-3m-12 3c0 1.232.046 2.453.138 3.662a4.006 4.006 0 003.7 3.7 48.656 48.656 0 007.324 0 4.006 4.006 0 003.7-3.7c.017-.22.032-.441.046-.662M4.5 12l3 3m-3-3l-3 3"
/>
</svg>
</span>
);
};

export default Loop;
47 changes: 47 additions & 0 deletions src/plays/audio-visualizer/components/Mike/Mike.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import React from "react";

type MikeProps = {
toggleMic: () => void;
useMic: boolean;
};

export const Mike = ({ toggleMic, useMic }: MikeProps) => {
return (
<span className="svg-circle">
{!useMic ? (
<svg
className="aud-svg"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
strokeWidth={1.5}
stroke="#1D1E2C"
onClick={toggleMic}
>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M12 18.75a6 6 0 006-6v-1.5m-6 7.5a6 6 0 01-6-6v-1.5m6 7.5v3.75m-3.75 0h7.5M12 15.75a3 3 0 01-3-3V4.5a3 3 0 116 0v8.25a3 3 0 01-3 3z"
/>
</svg>
) : (
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
strokeWidth={1.5}
stroke="#1D1E2C"
onClick={toggleMic}
className="play aud-svg"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M5.25 5.653c0-.856.917-1.398 1.667-.986l11.54 6.348a1.125 1.125 0 010 1.971l-11.54 6.347a1.125 1.125 0 01-1.667-.985V5.653z"
/>
</svg>
)}
</span>
);
};
export default Mike;