Wizualizacja muzyki [pierwsze porażki i sukcesy]

in #polish4 years ago

Wizualizacje i sztuka generyczna stały się ostatnio obiektem mojego zainteresowania, dlatego postanowiłem spróbować swoich własnych sił na tym polu.

Efektem tego jest zbudowanie wizualizera muzyki, przedstawiającego dynamicznie zmiany w sile sygnału na różnych częstotliwościach. Wszystko działa w środowisku przeglądarki internetowej, bazując na javascripcie, Canvasie, Web Audio API.

Całość funkcjonuje następująco:
dane o poziomie i zakresie częstotliwości są najpierw pobierane z ścieżki dźwiękowej przy użyciu obiektów z Web Audio API, a potem w czasie rzeczywistym wyświetlane są na Canvasie.

image.png

Obróbka dźwięku

Żeby pobrać dane z utworu muzycznego potrzebne są 3 obiekty:

let audioContext = new AudioContext();

Niezbędny do tworzenia innych obiektów z tego API.

let analyser = audioContext.createAnalyser();

Używany do pobrania danych o zakresie i poziomie częstotliwości.

let source = audioContext.createMediaElementSource(/* Jakiś znacznik audio */);

Pozwala wykorzystywać API dane pobierane z elementów audio

Po ich stworzeniu należy ustanowić odpowiednie relacje między nimi, żeby można było później korzystać z informacji Audio, które będą przez nie przesyłane:

source.connect(analyser);
source.connect(audioContext.destination);

Żeby pobrać dane z analysera potrzebne jest wcześniejsze stworzenie tablicy, która zostanie wypełniona danymi:

let data = new Uint8Array(analyser.frequencyBinCount);

frequencyBinCount to informacja na ile częstotliwości zostanie podzielony sygnał

analyser.getByteFrequencyData(data);

Tablica jest wypełniana danymi o wartościach kanałów na poszczególnych częstotliwościach


Wizualizacja

Żeby wyświetlić uzyskane w ten sposób dane potrzebny jest znacznik canvas i jego context umożliwiający dynamiczne rysowanie na tym elemencie strony.

let context = document.querySelector(/* identyfikator canvasa */).getContext('2d');

Żeby wyświetlić dane pozyskane z Web Audio API wykorzystuje się kolejne instrukcje

context.clearRect(0,0,context.canvas.width,context.canvas.height);

Wymazuje dotychczasową zawartość canvasa

for(let i = 0 ; i < data.length ; i++){
    let x = i * (context.canvas.width / data.length);
    let y = context.canvas.height - ((context.canvas.height) * (data[i] / 255));
    let width = (context.canvas.width / data.length);
    let height = (data[i] / 255) * context.canvas.height;
    this.context.fillRect(x,y,width,height);
}

Rysuje kolejne słupki dla wartości pobranych z Web Audio API


Żeby umożliwić płynną animacje instrukcje pozyskujące dane i rysujące należy umieścić wewnątrz funkcji podanej jako argument do requestAnimationFrame(), zagwarantuje to płynne generowanie kolejnych kadrów

requestAnimationFrame(loop);

Działający przykład:

Plik HTML:

<html>
    <head>
        <script src="visualizer.js" defer></script>
        <link rel="Stylesheet" href="visualizer.css">
    </head>
    <body>
        <button>Start</button>
        <audio>
            <source src="music.mp3">
        </audio>
        <canvas>
    </body>
</html>

Plik Visualizer.js:

let Audio_API,Visual;
function start(){
    function get_audio_API_control(){ /* OBRÓBKA DŹWIĘKU */
        let audioContext = new AudioContext();
        let analyser = audioContext.createAnalyser();
        let source = audioContext.createMediaElementSource(document.querySelector('audio'));
        source.connect(analyser);
        source.connect(audioContext.destination);
        return {
            audioContext:audioContext,
            analyser:analyser,
            source:source,
            data_array: new Uint8Array(analyser.frequencyBinCount),
            get_frequency:function(){ /* Zwraca dane o sile sygnału na częstot. */
    this.analyser.getByteFrequencyData(this.data_array);
                return this.data_array;
            }
        };
    }
if(typeof(Audio_API) == 'undefined') Audio_API = get_audio_API_control();

    function get_canvas_API_control(Audio_API){   /* WIZUALIZACJA */
        let context = document.querySelector('canvas').getContext('2d');
        return{
            context:context,
            audio:Audio_API,
            draw:function(){
            let data = this.audio.get_frequency();
                this.context.clearRect(0,0,this.context.canvas.width,this.context.canvas.height);
                for(let i = 0 ; i < data.length ; i++){
                    let x = i * (this.context.canvas.width / data.length);
                    let y = this.context.canvas.height - ((this.context.canvas.height) * (data[i] / 255));
                    let width = this.context.canvas.width / data.length;
                    let height = (data[i] / 255) * this.context.canvas.height;
                    this.context.fillRect(x,y,width,height);
                }
            }
        }
    }
    if(typeof(Visual) == 'undefined') Visual = get_canvas_API_control(Audio_API);
        
    document.querySelector('audio').play();
    function loop(){
        Visual.draw();
        requestAnimationFrame(loop);
    }
    loop();
}

document.body.querySelector('button').addEventListener('click',()=>{start()});

Z działania całości jestem zadowolony chociaż trzeba by było jeszcze sporo od strony graficznej poprawić.

CSS dołączam w ramach ciekawostki:

body{
    margin:0;
}

canvas{
    width:100vw;
    height:100vw;
    position:fixed;
    top:0;
    left:0;
    z-index:1;
    background-color:darkcyan;
}

button{
    display:block;
    position:fixed;
    z-index:2;
    top:5vw;
    left:50vw;
    transform:translateX(-50%);
    padding:1%;
    border-radius: 20%;
    font-weight: bold;
    background-color: transparent;
    border-color: white;
    color: white;
    cursor: pointer;
}

Cały projekt wygląda u mnie tak:
image.png

Sort:  

Congratulations @kraken14! You have completed the following achievement on the Hive blockchain and have been rewarded with new badge(s) :

You published more than 40 posts. Your next target is to reach 50 posts.

You can view your badges on your board and compare yourself to others in the Ranking
If you no longer want to receive notifications, reply to this comment with the word STOP

Do not miss the last post from @hivebuzz:

Feedback from the October 1st Hive Power Up Day
Hive Power Up Day - Introducing the Power Up Helper!