여러 오디오 소스를 동적으로 처리할 수 있는 오디오 시각화 도구를 만듭니다. - 모두 Vanilla JS에 있습니다!
29638 단어 showdevjavascripttutorial
우선, 시각화 도구 자체의 주요 출처로 연결해 드리겠습니다.JS에서 어떻게 Web Audio API을 사용하여 오디오 상하문을 처리하는지 알아보기 위해 나는 이 CodePen을 인용하여 간단한 단원 수준의 시각화 도구를 만들었다.시작하고 실행한 후에, 나는 시각화된 모양을 다시 써서 동그라미를 둘러싸기로 결정했다.이를 위해 나는 step-by-step guide을 인용했다.나는 이 실현을 중점적으로 소개할 것이다. 왜냐하면 그것은 내가 여러 가지 원천을 실현하는 데 쓰이기 때문이다.
주의//이것은 브라우저에서 시각화 도구를 실현하는 가장 효과적인 방법이 아니라고 의심하지 않습니다.일단 여러 개의 오디오 원본을 추가하거나 보통 더 큰 파일을 추가하면 클라이언트에게 이것은 상당히 큰 부하이다.그럼에도 불구하고 할 수 있다. 가방이나 프레임이 필요 없다는 것을 감안하면 멋있다고 말하고 싶다.
내 프로그램에서 모든 소리는 특정한 화훼 대상과 연결되어 변수 이름의 화훼 주제에 대해 호기심을 느끼지 않도록 한다.
소리가 어떻게 나오는지 먼저 봅시다.
function createSound (flower) {
const sound = document.createElement('audio');
sound.id = flower.name; // set ID of sound to use as a key for global obj
sound.src = `./sounds/${flower.sound}.mp3`; // set source to locally stored file
sound.crossOrigin = "anonymous"; // avoid a CORS error
sound.loop = "true"; // sounds need to loop to the beginning after they end
sound.dataset.action = "off"; // for pausing feature
document.getElementById("audio-container").append(sound); // append sound to HTML container
allSoundsById[sound.id] = sound; // add to global object for later use
return sound; // return sound to parent function
}
소리가 페이지를 불러올 때 페이지에 나타날 때, 시작할 때 HTML createSound
함수를 호출하여 HTML <audio>
표시를 만들고, 전역 그룹을 채웁니다. 이 그룹은 id (이 예에서 연결된 꽃의 이름) 를 키로 하고, 요소를 값으로 합니다.꽃마다 연결된 '클릭' 이벤트 감청기가 있습니다. 먼저 소리를 재생한 다음에
renderVisualizer
함수를 호출합니다. 이 함수는 현재 페이지에 재생된 소리 데이터를 실제적으로 보여 줍니다.다음은 이 함수를 살펴보자.우리가 여러 개의 소리 입력의 본질을 깊이 이해하기 전에, 나는 먼저 시각화 도구가 어떻게 설정되었는지 소개하고 싶다.이것은 HTML5 캔버스 요소에 그려진 것으로 애니메이션 프레임을 렌더링할 때 이 요소의 중심에 원을 그립니다.그것은 가시화 도구의 줄과 같은 고정 수량의 부분으로 나뉜다.각 막대는 하나의 주파수 데이터와 연결되어 있으며 애니메이션 프레임을 렌더링할 때마다 주파수 데이터의 높이는 소리의 변화에 따라 달라집니다.따라서 너비는 고정되어 있고 높이는 소리가 끊임없이 변화하는 주파수 정보를 대표한다(무엇이 소리를 움직이게 하는가!).만약 당신이 이 기초가 어떻게 작동하는지 더욱 투철하게 이해하고 싶다면, 본문 끝에 연결된 나의 참고 자료를 참고하십시오.
페이지에 있는 canvas 요소를 먼저 방문합시다.스크립트 파일에서 만들거나 HTML로 준비된 HTML 요소일 뿐입니다.나는 후자를 만들었다.이어서 HTML 캔버스의 상하문을 받아야 한다. 우리가 사용하는 것은 3D가 아니라 2D이다.
canvasContext
은 우리가 그릴 내용입니다. -canvas
은 바로 DOM 요소와 같습니다. function renderVisualizer () {
// Get canvas
const canvas = document.getElementById("vis");
const canvasContext = canvas.getContext("2d");
다음에, 우리는 모든 소리를 위해 오디오 상하문을 만들어야 한다.이것이 바로 우리가 모든 훌륭한 데이터에 접근할 수 있는 이유이다.내가 전에 말했듯이 모든 소리는 하나의 전역 대상에 저장되어 나중에 사용할 수 있다. 이것이 바로 우리가 사용할 곳이다.객체의 각 사운드 키 - 값 쌍에 대해 같은 키를 사용하여 다른 객체를 만들고 값을 필요한 정보로 설정합니다. Object.keys(allSoundsById).forEach((id) => {
// condition to avoid creating duplicate context. the visualizer won't break without it, but you will get a console error.
if (!audioContextById[id]) {
audioContextById[id] = createAudioContextiObj(allSoundsById[id])
}
})
...다음은 createAudioContextObj
함수입니다. function createAudioContextiObj (sound) {
// initialize new audio context
const audioContext = new AudioContext();
// create new audio context with given sound
const src = audioContext.createMediaElementSource(sound);
// create analyser (gets lots o data bout audio)
const analyser = audioContext.createAnalyser();
// connect audio source to analyser to get data for the sound
src.connect(analyser);
analyser.connect(audioContext.destination);
analyser.fftSize = 512; // set the bin size to condense amount of data
// array limited to unsigned int values 0-255
const bufferLength = analyser.frequencyBinCount;
const freqData = new Uint8Array(bufferLength);
audioContextObj = {
freqData, // note: at this time, this area is unpopulated!
analyser
}
return audioContextObj;
}
여기서, 우리는 오디오 상하문을 만들고, 소리를 연결시키고, 나중에 부모 함수에서 사용할 수 있도록 필요한 도구를 대상에 되돌려줍니다.나는 또한 fftSize
(빠른 푸리엽 변환을 뜻함)을 512로 설정했다. 기본값은 2048이다. 우리는 그렇게 많은 데이터를 필요로 하지 않기 때문에 압축했다.freqData
어레이의 길이를 256으로 늘릴 수 있습니다. 130개 스트라이프를 고려하면 더 적합합니다!나는 이 점에서 이것이 좀 복잡해질 수 있다는 것을 안다.여기서 일어나는 일들을 이해하는 세부사항은 중요하지 않다고 말하고 싶지는 않지만, 여기서 일어나는 일들을 완전히 이해할 수는 없다.본질적으로, 우리는 우리에게 제공된 도구를 사용하여 음성 주파수에 대한 정보를 얻고 있으며, 이러한 정보를 사용하여 시각화 효과를 그릴 것이다.우리 계속 전진합시다.
renderFrame
의 renderVisualizer
함수를 호출하기 전에 고정된 수량의 줄을 설정하고 그에 상응하는 폭을 설정하며 높이 변수를 초기화합니다. const numBars = 130;
let barWidth = 3;
let barHeight;
자, 이제 우리는 깊이 토론할 수 있다.우리는 renderFrame
함수에 있다.그것은 데이터를 연속적으로 보여주고 그것을 캔버스에 그리는 것을 책임진다. function renderFrame() {
const freqDataMany = []; // reset array that holds the sound data for given number of audio sources
const agg = []; // reset array that holds aggregate sound data
canvasContext.clearRect(0, 0, canvas.width, canvas.height) // clear canvas at each frame
requestAnimationFrame(renderFrame); // this defines the callback function for what to do at each frame
audioContextArr = Object.values(audioContextById); // array with all the audio context information
// for each element in that array, get the *current* frequency data and store it
audioContextArr.forEach((audioContextObj) => {
let freqData = audioContextObj.freqData;
audioContextObj.analyser.getByteFrequencyData(freqData); // populate with data
freqDataMany.push(freqData);
})
if (audioContextArr.length > 0) {
// aggregate that data!
for (let i = 0; i < freqDataMany[0].length; i++) {
agg.push(0);
freqDataMany.forEach((data) => {
agg[i] += data[i];
});
}
좋아, 이거 코드가 많아!한 걸음 한 걸음 갑시다.우선, 매 프레임마다 renderFrame
함수를 호출한다.우리가 해야 할 첫 번째 일은 모든 주파수 데이터의 실례를 저장하는 그룹을 리셋하고, 이 데이터를 함께 추가하는 그룹을 리셋하는 것이다.오디오 상하문에 있는 모든 주파수 데이터는 현재 채워지지 않은 그룹으로 설정되어 있으며, 이 그룹은 각자의 분석기로 채워질 것입니다.도대체 생각해 보자. freqDataMany = [ [freqDataForFirstSound], [freqDataForSecondSound], [freqDataForThirdSound]....];
agg = [[allFreqDataAddedTogether]];
호기심 때문에 agg
의 단편이 있는데 그 중에는 몇 가지 데이터가 포함되어 있다.이거 별거 아니야?나중에 집계된 데이터에 대해 더 많은 작업을 수행하지만 먼저 원을 그리고 다음과 같은 막대를 그립니다.
// still inside if (audioContextArr.length > 0)
// set origin of circle to center of canvas
const centerX = canvas.width / 2;
const centerY = canvas.height / 2;
const radius = 50; // set size of circle based on its radius
// draw circle
canvasContext.beginPath();
canvasContext.arc(centerX, centerY, radius, 0, (2*Math.PI) );
canvasContext.lineWidth = 1;
canvasContext.stroke();
canvasContext.closePath()
주의//항상 캔버스에 원을 그리려면 renderFrame
함수 이외에 작성할 수 있습니다.만약 소리가 없다면, 나는 화포가 완전히 또렷하길 바란다.이것이 바로 마법이 발생하는 곳이다.각 애니메이션 프레임에서 발생하는 렌더링마다 주기가 130번 실행됩니다(위에서 정의한 줄 수).그것은 원 주위의 모든 줄을 그리는 것을 책임진다.
for (let i = 0; i < (numBars); i++) {
barHeight = (agg[i] * 0.4);
let rads = (Math.PI * 2) / numBars;
let x = centerX + Math.cos(rads * i) * (radius);
let y = centerY + Math.sin(rads * i) * (radius);
let x_end = centerX + Math.cos(rads * i) * (radius + barHeight);
let y_end = centerY + Math.sin(rads * i) * (radius + barHeight);
drawBar(canvasContext, x, y, x_end, y_end, barWidth)
}
스트라이프 높이는 집합 주파수 데이터 그룹의 i
위 정보로 동적으로 설정됩니다.우리들은 이 일을 분명히 합시다.주파수 데이터는 265개의 상자로 나뉜다.agg[0]
은 첫 번째 상자, agg[1]
은 두 번째...agg[130]
은 130위다.numBars
을 256으로 설정하여 스토리지의 각 빈도 데이터에 액세스할 수 있습니다.그러나 나는 더 높은 주파수를 낮추고 낮은 술집 수를 선호한다. (고주파 조류의 지저귀는 소리를 규범화시켰다.)그 밖에 나는 모든 물건을 캔버스 위에 놓을 수 있도록 0.4를 곱해서 줄의 높이를 제한했다.우리 수학에 들어가자.두려워하지 마라. 삼각형이 있어야만 우리가 동그라미를 따라 가로대를 그릴 수 있다.
rads
은 원을 호도로 바꾸고 있습니다. 이것은 우리에게 더욱 쉽습니다.우리는 일반적인 공식을 사용하여 극좌표(호도 사용)를 피리칼 좌표(또는 우리가 잘 아는 친구(x, y)로 변환할 것이다.x = radius × cos( θ )
y = radius × sin( θ )
너는 그것의 작업 원리를 더욱 깊이 이해할 수 있지만 (아래 링크 참조), 만약 네가 계속하고 싶다면, 우리가 이 공식을 사용해서 조형도의 출발점과 종점 좌표를 확정하고 있다는 것만 알면 된다.그것의 시작점은 원주 위의 한 점(이것이 바로 위 공식의 용도)이고 우리가 순환하는 어느 주기에 따라 점차적으로 증가해야 한다(이것이 바로 우리가 그것을
i
에 곱해야 하는 이유이다.그렇지 않으면 그것들은 서로 중첩될 것이다).단점은 barHeight
을 기반으로 하고, 기억하고 있다면 agg
그룹과 관련된 비트 데이터를 기반으로 합니다.필요한 모든 좌표와 주기에 앞서 정의된 고정 너비를 사용하여 막대 그래프를 그릴 수 있습니다. function drawBar(canvasContext, x1, y1, x2, y2, width){
const gradient = canvasContext.createLinearGradient(x1, y1, x2, y2); // set a gradient for the bar to be drawn with
// color stops for the gradient
gradient.addColorStop(0, "rgb(211, 197, 222)");
gradient.addColorStop(0.8, "rgb(255, 230, 250)");
gradient.addColorStop(1, "white");
canvasContext.lineWidth = width; // set line width equal to passed in width
canvasContext.strokeStyle = gradient; // set stroke style to gradient defined above
// draw the line!
canvasContext.beginPath();
canvasContext.moveTo(x1,y1);
canvasContext.lineTo(x2,y2);
canvasContext.stroke();
canvasContext.closePath();
}
우리는 곧 도착할 것이다.우리가 지금 해야 할 일은 정확한 시간에 모든 함수를 호출하는 것을 확보하는 것이다.가능한 한 많은 것이 붕괴된 후, 다음은 renderVisualizer
함수이다.renderFrame
함수 정의 후에 우리는 그것을 직접 호출합니다.사운드를 처음 재생할 때 를 클릭하면 renderVisualizer
함수가 호출됩니다.클릭을 통해 다른 소리를 층으로 나눌 때 주파수 데이터는 현재 주파수 데이터로 집합됩니다.소리를 멈출 때 주파수 데이터가 없습니다. freqData
과 agg
은 렌더링 프레임마다 리셋됩니다.만약 한 소리가 재생되지 않는다면, 그것은 freqData
일 뿐이다. 현재 재생된 소리와 집합할 때, 그 소리는 추가할 데이터가 전혀 없다.다음은 실행 중인gif 사진입니다.
크기가 적당한 GIF를 얻기 위해 나는 시각화 도구에서만 화면 기록을 했다.첫 번째 사운드를 추가한 다음 다른 사운드(가로대 높이가 뛰고 특히 왼쪽 아래) - 두 번째 사운드 소스가 제거되고 첫 번째 사운드 소스도 제거됩니다.
봐라!나는 단지 며칠 만에 이 점을 실현했기 때문에 나는 당연히 어떠한 최적화나 비판도 받아들이고 싶다.다음은 내가 사용한 참고 문헌의 유용한 목록이다.
Reference
이 문제에 관하여(여러 오디오 소스를 동적으로 처리할 수 있는 오디오 시각화 도구를 만듭니다. - 모두 Vanilla JS에 있습니다!), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://dev.to/rizz0s/creating-an-audio-visualizer-that-can-handle-multiple-audio-sources-dynamically-all-in-vanilla-js-5hfl텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)