


Faire un visualiseur de forme d'onde audio avec Javascript Vanilla
En tant que concepteur d'interface utilisateur, je me souviens constamment de la valeur de savoir comment coder. Je suis fier de penser aux développeurs de mon équipe tout en concevant des interfaces utilisateur. Mais parfois, je marche sur une mine technique.
Il y a quelques années, en tant que directeur du design de WSJ.com, j'ai aidé à refaire le répertoire du podcast du Wall Street Journal. L'un des concepteurs du projet travaillait sur le lecteur de podcast, et je suis tombé sur le joueur intégré de Megaphone.
J'ai déjà travaillé sur SoundCloud et je savais que ces types de visualisations étaient utiles pour les utilisateurs qui sautent audio. Je me demandais si nous pouvions obtenir un look similaire pour le joueur sur le site du Wall Street Journal.
La réponse de l'ingénierie: certainement pas. Compte tenu des délais et des contraintes, ce n'était pas une possibilité pour ce projet. Nous avons finalement expédié les pages redessinées avec un lecteur de podcast beaucoup plus simple.
Mais j'étais accro au problème. Pendant les nuits et les week-ends, j'ai piraté en essayant de réaliser cet effet. J'ai beaucoup appris sur le fonctionnement de l'audio sur le Web et j'ai finalement pu réaliser le look avec moins de 100 lignes de JavaScript!
Il s'avère que cet exemple est un moyen idéal pour se familiariser avec l'API audio Web et comment visualiser les données audio à l'aide de l'API Canvas.
Mais d'abord, une leçon sur le fonctionnement de l'audio numérique
Dans le monde analogique réel, le son est une vague. Au fur et à mesure que le son passe d'une source (comme un haut-parleur) à vos oreilles, il comprime et décompresse l'air dans un modèle que vos oreilles et votre cerveau entendent comme de la musique, ou une parole, ou un écorce de chien, etc. etc.
Mais dans le monde des signaux électroniques d'un ordinateur, le son n'est pas une vague. Pour transformer une onde continue et continue en données qu'il peut stocker, les ordinateurs font quelque chose appelé échantillonnage . L'échantillonnage signifie mesurer les ondes sonores frappant un microphone des milliers de fois à chaque seconde, puis en stockant ces points de données. Lors de la lecture de l'audio, votre ordinateur inverse le processus: il recrée le son, une minuscule fractionnement audio à la fois.
Le nombre de points de données dans un fichier son dépend de sa fréquence d'échantillonnage . Vous avez peut-être déjà vu ce nombre; La fréquence d'échantillonnage typique pour les fichiers MP3 est de 44,1 kHz. Cela signifie que, pour chaque seconde d'audio, il y a 44 100 points de données individuels. Pour les fichiers stéréo, il y a 88 200 toutes les secondes - 44 100 pour le canal gauche et 44 100 pour la droite. Cela signifie qu'un podcast de 30 minutes a 158 760 000 points de données individuels décrivant l'audio!
Comment une page Web peut-elle lire un MP3?
Au cours des neuf dernières années, le W3C (les gens qui aident à maintenir les normes Web) ont développé l'API audio Web pour aider les développeurs Web à travailler avec l'audio. L'API audio Web est un sujet très profond; Nous allons à peine casser la surface dans cet essai. Mais tout commence par quelque chose appelé audioconText.
Pensez à l'audiocontext comme un bac à sable pour travailler avec l'audio. Nous pouvons l'initialiser avec quelques lignes de JavaScript:
// Configuration du contexte audio window.audiocontext = window.audioconText || window.webkitaudioConText; const AudioConText = new AudioConText (); Soit CurrentBuffer = NULL;
La première ligne après le commentaire est nécessaire car Safari a implémenté AudioconText en tant que webkitaudioconText.
Ensuite, nous devons donner à notre nouvel audioText le fichier MP3 que nous aimerions visualiser. Remplissons-le en utilisant… fetch ()!
const visualizeAudio = url => { Fetch (URL) .Then (Response => Response.ArrayBuffer ()) .Then (ArrayBuffer => AudioconText.decodeAudiodata (ArrayBuffer)) .Then (AudioBuffer => Visualize (AudioBuffer)); };
Cette fonction prend une URL, la récupére, puis transforme l'objet de réponse plusieurs fois.
- Tout d'abord, il appelle la méthode ArrayBuffer (), qui revient - vous l'avez deviné - un ArrayBuffer! Un ArrayBuffer n'est qu'un conteneur pour les données binaires; C'est un moyen efficace de déplacer de nombreuses données dans JavaScript.
- Nous envoyons ensuite le ArrayBuffer à notre audiocontext via la méthode decodeauData (). DecodeAuData () prend un ArrayBuffer et renvoie un Audiobuffer, qui est un ArrayBuffer spécialisé pour la lecture des données audio. Saviez-vous que les navigateurs sont venus avec tous ces objets pratiques? Je ne l'ai certainement pas fait quand j'ai commencé ce projet.
- Enfin, nous envoyons notre Audiobuffer pour être visualisé.
Filtrer les données
Pour visualiser notre Audiobuffer, nous devons réduire la quantité de données avec lesquelles nous travaillons. Comme je l'ai déjà mentionné, nous avons commencé avec des millions de points de données, mais nous aurons beaucoup moins dans notre visualisation finale.
Tout d'abord, limitons les canaux avec lesquels nous travaillons. Un canal représente l'audio envoyé à un haut-parleur individuel. En son stéréo, il y a deux canaux; Dans 5.1 Sound Sound, il y en a six. Audiobuffer a une méthode intégrée pour ce faire: getChannelData (). Appelez Audiobuffer.getChanneldata (0), et nous nous retrouverons avec une valeur de données d'un canal.
Ensuite, la partie dure: Loop à travers les données du canal et sélectionnez un ensemble plus petit de points de données. Il y a quelques façons de procéder à ce sujet. Disons que je veux que ma visualisation finale ait 70 bars; Je peux diviser les données audio en 70 parties égales et regarder un point de données de chacun.
const filterData = Audiobuffer => { const rawdata = audiobuffer.getChannelData (0); // Nous avons seulement besoin de travailler avec un canal de données const échantillons = 70; // Nombre d'échantillons que nous voulons avoir dans notre ensemble de données final const BlockSize = math.floor (RawData.Length / Samples); // Nombre d'échantillons dans chaque subdivision const FilteredData = []; pour (soit i = 0; i <p> La sortie m'a pris au dépourvu! Cela ne ressemble pas du tout à la visualisation que nous émulons. Il y a beaucoup de points de données proches ou à zéro. Mais cela a beaucoup de sens: dans un podcast, il y a beaucoup de silence entre les mots et les phrases. En ne regardant que le premier échantillon dans chacun de nos blocs, il est très probable que nous prendrons un moment très calme.</p><p> Modifions l'algorithme pour trouver la <em>moyenne</em> des échantillons. Et même si nous y sommes, nous devons prendre la valeur absolue de nos données, afin que tout soit positif.</p><pre rel="JavaScript" data-line=""> const filterData = Audiobuffer => { const rawdata = audiobuffer.getChannelData (0); // Nous avons seulement besoin de travailler avec un canal de données const échantillons = 70; // Nombre d'échantillons que nous voulons avoir dans notre ensemble de données final const BlockSize = math.floor (RawData.Length / Samples); // le nombre d'échantillons dans chaque subdivision const FilteredData = []; pour (soit i = 0; i <p> Voyons à quoi ressemble ces données.</p><p> C'est génial. Il ne reste qu'une chose à faire: parce que nous avons tellement de silence dans le fichier audio, les moyennes résultantes des points de données sont très petites. Pour nous assurer que cette visualisation fonctionne pour tous les fichiers audio, nous devons <em>normaliser</em> les données; Autrement dit, modifiez l'échelle des données afin que les échantillons les plus bruyants mesurent 1.</p><pre rel="JavaScript" data-line=""> const normalizedata = filteredData => { const Multiplier = Math.Pow (math.max (... filteredData), -1); return filteredData.map (n => n * multiplicateur); }
Cette fonction trouve le plus grand point de données dans le tableau avec math.max (), prend son inverse avec math.pow (n, -1) et multiplie chaque valeur dans le tableau par ce nombre. Cela garantit que le point de données le plus important sera défini sur 1, et le reste des données évoluera proportionnellement.
Maintenant que nous avons les bonnes données, écrivons la fonction qui la visualisera.
Visualiser les données
Pour créer la visualisation, nous utiliserons l'API JavaScript Canvas. Cette API attire des graphiques dans un HTML élément. La première étape de l'utilisation de l'API Canvas est similaire à l'API Web Audio.
const Draw = normaliséData => { // configure la toile const canvas = document.QuerySelector ("canvas"); const dpr = window.devicePixelratio || 1; Padding de const = 20; canvas.width = canvas.offsetwidth * dpr; canvas.height = (canvas.offsetheight padding * 2) * dpr; const ctx = canvas.getContext ("2d"); CTX.SCALE (DPR, DPR); ctx.translate (0, canvas.offsetheight / 2 padding); // Définit y = 0 pour être au milieu de la toile };
Ce code trouve l'élément
Maintenant, dessinons quelques lignes! Tout d'abord, nous créerons une fonction qui dessinera un segment individuel.
const DrawLinesegment = (ctx, x, y, largeur, iseven) => { ctx.linewidth = 1; // Quelle est l'épaisseur de la ligne ctx.strokestyle = "#fff"; // quelle couleur est notre ligne ctx.beginPath (); y = iseven? y: -y; ctx.moveto (x, 0); ctx.lineto (x, y); ctx.arc (x largeur / 2, y, largeur / 2, math.pi, 0, iseven); ctx.lineto (x largeur, 0); ctx.stroke (); };
L'API Canvas utilise un concept appelé «Turtle Graphics». Imaginez que le code est un ensemble d'instructions données à une tortue avec un marqueur. En termes de base, la fonction DrawLinesegment () fonctionne comme suit:
- Commencez à la ligne centrale, x = 0.
- Tracer une ligne verticale. Faire la hauteur de la ligne par rapport aux données.
- Dessinez un demi-cercle la largeur du segment.
- Reprenez une ligne verticale sur la ligne médiane.
La plupart des commandes sont simples: ctx.moveto () et ctx.lineto () déplacez la tortue vers la coordonnée spécifiée, sans dessin ni dessin, respectivement.
Ligne 5, y = Iseven? -y: y, dit à notre tortue de dessiner ou de tirer de la ligne médiane. Les segments alternent entre être au-dessus et en dessous de la ligne médiane afin qu'ils forment une onde lisse. Dans le monde de l'API sur toile, les valeurs Y négatives sont plus élevées que les valeurs positives. C'est un peu contre-intuitif, alors gardez-le à l'esprit en tant que source possible de bogues.
Sur la ligne 8, nous dessinons un demi-cercle. ctx.arc () prend six paramètres:
- Les coordonnées X et Y du centre du cercle
- Le rayon du cercle
- L'endroit dans le cercle pour commencer à dessiner (math.pi ou π est l'emplacement, en radians, de 9 heures)
- L'endroit dans le cercle pour terminer le dessin (0 en radians représente 3 heures)
- Une valeur booléenne indiquant à notre tortue de dessiner dans le sens antihoraire (si vrai) ou dans le sens des aiguilles d'une montre (si false). L'utilisation d'ISEVEN dans ce dernier argument signifie que nous dessinerons la moitié supérieure d'un cercle - dans le sens horaire de 9 heures à 3 horloge - pour les segments uniformes et la moitié inférieure pour les segments impairs.
Ok, retour à la fonction Draw ().
const Draw = normaliséData => { // configure la toile const canvas = document.QuerySelector ("canvas"); const dpr = window.devicePixelratio || 1; Padding de const = 20; canvas.width = canvas.offsetwidth * dpr; canvas.height = (canvas.offsetheight padding * 2) * dpr; const ctx = canvas.getContext ("2d"); CTX.SCALE (DPR, DPR); ctx.translate (0, canvas.offsetheight / 2 padding); // Définit y = 0 pour être au milieu de la toile // dessine les segments de ligne const width = canvas.offsetwidth / normaliséData.length; pour (soit i = 0; i <normalis i const x="largeur" let haged="normaliséData" canvas.offsetheight padding if hauteur="0;" else> canvas.offsetheight / 2) { hauteur = hauteur> canvas.offsetheight / 2; } Drawlinesegment (ctx, x, hauteur, largeur, (i 1)% 2); } };</normalis>
Après notre code de configuration précédent, nous devons calculer la largeur des pixels de chaque segment de ligne. Il s'agit de la largeur à l'écran de la toile, divisée par le nombre de segments que nous aimerions afficher.
Ensuite, une boucle pour chaque entrée dans le tableau et dessine un segment de ligne en utilisant la fonction que nous avons définie précédemment. Nous définissons la valeur X sur l'index de l'itération actuelle, fois la largeur du segment. Hauteur, la hauteur souhaitée du segment provient de la multiplication de nos données normalisées par la hauteur de la toile, moins le rembourrage que nous avons fixé plus tôt. Nous vérifions quelques cas: la soustraction du rembourrage a peut-être poussé la hauteur dans le négatif, nous avons donc réinitialisé cela à zéro. Si la hauteur du segment entraînera une ligne tracée en haut de la toile, nous réinitialisons la hauteur à une valeur maximale.
Nous passons dans la largeur du segment, et pour la valeur de l'ISEVEN, nous utilisons une astuce soignée: (i 1)% 2 signifie «Trouvez le reste de i 1 divisé par 2.» Nous vérifions I 1 parce que notre compteur commence à 0. Si je 1 est égal, son reste sera nul (ou faux). Si je suis étrange, son reste sera 1 ou vrai.
Et c'est tout ce qu'elle a écrit. Mettons tout cela ensemble. Voici tout le script, dans toute sa gloire.
Dans la fonction DrawAudio (), nous avons ajouté quelques fonctions à l'appel final: Draw (Normalizedata (filterData (Audiobuffer))). Cette chaîne filtre, normalise et tire enfin l'audio que nous récupérons du serveur.
Si tout s'est passé comme prévu, votre page devrait ressembler à ceci:
Notes sur les performances
Même avec des optimisations, ce script exécute probablement des centaines de milliers d'opérations dans le navigateur. Selon la mise en œuvre du navigateur, cela peut prendre plusieurs secondes pour terminer, et aura un impact négatif sur les autres calculs qui se produisent sur la page. Il télécharge également l'ensemble du fichier audio avant de dessiner la visualisation, qui consomme beaucoup de données. Il y a quelques façons d'améliorer le script pour résoudre ces problèmes:
- Analysez l'audio côté serveur. Étant donné que les fichiers audio ne changent pas souvent, nous pouvons profiter des ressources informatiques côté serveur pour filtrer et normaliser les données. Ensuite, nous n'avons qu'à transmettre l'ensemble de données plus petit; Pas besoin de télécharger le MP3 pour dessiner la visualisation!
- Dessinez uniquement la visualisation lorsqu'un utilisateur en a besoin. Peu importe comment nous analysons l'audio, il est logique de reporter le processus jusqu'à la charge de page. Nous pourrions attendre que l'élément soit en vue à l'aide d'un observateur d'intersection, soit retarder encore plus longtemps jusqu'à ce qu'un utilisateur interagit avec le lecteur de podcast.
- Amélioration progressive. En explorant le lecteur de podcast de Megaphone, j'ai découvert que leur visualisation n'est qu'une façade - c'est la même forme d'onde pour chaque podcast. Cela pourrait servir de défaut à notre conception (largement supérieure). En utilisant les principes de l'amélioration progressive, nous pourrions charger une image par défaut en tant qu'ensembles d'espace réservé. Ensuite, nous pouvons vérifier s'il est logique de charger la forme d'onde réelle avant de lancer notre script. Si l'utilisateur est désactivé JavaScript, son navigateur ne prend pas en charge l'API audio Web, ou il a l'en-tête de données de sauvegarde, rien n'est cassé.
J'aimerais aussi entendre toutes les pensées que vous avez sur l'optimisation.
Quelques pensées de clôture
C'est une façon très, très peu pratique de visualiser l'audio. Il s'exécute du côté client, traitant des millions de points de données dans une visualisation assez simple.
Mais c'est cool ! J'ai beaucoup appris en écrivant ce code, et encore plus en écrivant cet article. J'ai refactorisé une grande partie du projet original et rédigé le tout en deux. Des projets comme celui-ci pourraient ne jamais voir une base de code de production, mais ce sont des opportunités uniques de développer de nouvelles compétences et une compréhension plus approfondie de certains des supports de navigateurs modernes API soignés.
J'espère que c'était un tutoriel utile. Si vous avez des idées sur la façon de l'améliorer, ou des variations intéressantes sur le thème, veuillez contacter! Je suis @ilikescience sur Twitter.
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Outils d'IA chauds

Undresser.AI Undress
Application basée sur l'IA pour créer des photos de nu réalistes

AI Clothes Remover
Outil d'IA en ligne pour supprimer les vêtements des photos.

Undress AI Tool
Images de déshabillage gratuites

Clothoff.io
Dissolvant de vêtements AI

Video Face Swap
Échangez les visages dans n'importe quelle vidéo sans effort grâce à notre outil d'échange de visage AI entièrement gratuit !

Article chaud

Outils chauds

Bloc-notes++7.3.1
Éditeur de code facile à utiliser et gratuit

SublimeText3 version chinoise
Version chinoise, très simple à utiliser

Envoyer Studio 13.0.1
Puissant environnement de développement intégré PHP

Dreamweaver CS6
Outils de développement Web visuel

SublimeText3 version Mac
Logiciel d'édition de code au niveau de Dieu (SublimeText3)

Sujets chauds

L'autre jour, j'ai repéré ce morceau particulièrement charmant sur le site Web de Corey Ginnivan où une collection de cartes se cassent les uns sur les autres pendant que vous faites défiler.

Je vois que Google Fonts a déployé un nouveau design (tweet). Comparé à la dernière grande refonte, cela semble beaucoup plus itératif. Je peux à peine faire la différence

Avez-vous déjà eu besoin d'un compte à rebours sur un projet? Pour quelque chose comme ça, il pourrait être naturel d'atteindre un plugin, mais c'est en fait beaucoup plus

Questions sur les zones de slash violet dans les dispositions flexibles Lorsque vous utilisez des dispositions flexibles, vous pouvez rencontrer des phénomènes déroutants, comme dans les outils du développeur (D ...

Lorsque le nombre d'éléments n'est pas fixé, comment sélectionner le premier élément enfant du nom de classe spécifié via CSS. Lors du traitement de la structure HTML, vous rencontrez souvent différents éléments ...

Tout ce que vous avez toujours voulu savoir sur les attributs de données dans HTML, CSS et JavaScript.

Au début d'un nouveau projet, la compilation SASS se produit en un clin d'œil. Cela se sent bien, surtout quand il est associé à BrowSersync, qui recharge

Comment implémenter des fenêtres dans le développement frontal ...
