web-dev-qa-db-fra.com

Générer du son à la volée avec javascript / html5

Est-il possible de générer un flux sonore constant avec javascript/html5? Par exemple, pour générer une onde sinusoïdale perpétuelle, j'aurais une fonction de rappel, qui serait appelée chaque fois que le tampon de sortie est sur le point de devenir vide:

function getSampleAt(timestep)
{
    return Math.sin(timestep);
}

(L'idée est de l'utiliser pour faire un synthé interactif. Je ne sais pas à l'avance combien de temps une touche sera enfoncée, donc je ne peux pas utiliser un tampon de longueur fixe)

64
Johannes Hoff

Utilisation de l'élément audio HTML5

L'audio soutenu génératif multi-navigateur utilisant JavaScript et l'élément audio n'est actuellement pas possible, comme Steven Wittens notes dans un article de blog sur la création d'un synthétiseur JavaScript:

"... il n'y a aucun moyen de mettre en file d'attente des morceaux audio synthétisés pour une lecture fluide".

Utilisation de l'API Web Audio

Web Audio API a été conçu pour faciliter la synthèse audio JavaScript. Le Mozilla Developer Network a un générateur de tonalité basé sur le Web qui fonctionne dans Firefox 4+ [ démo 1 ]. Ajoutez ces deux lignes à ce code et vous disposez d'un synthétiseur fonctionnel avec un son génératif soutenu lors d'une pression de touche [ démo 2 - ne fonctionne que dans Firefox 4, cliquez d'abord sur la zone "Résultats", puis appuyez sur n'importe quelle touche]:

window.onkeydown = start;  
window.onkeyup = stop;

La BBC page sur l'API Web Audio mérite également d'être revue. Malheureusement, la prise en charge de l'API Web Audio ne s'étend pas encore aux autres navigateurs.

Solutions de contournement possibles

Pour créer un synthé multi-navigateur à l'heure actuelle, vous devrez probablement vous rabattre sur l'audio préenregistré en:

  1. En utilisant de longs échantillons de sons ogg/mp3 préenregistrés, en les incorporant dans des éléments audio séparés et en les démarrant et en les arrêtant lors d'une pression sur une touche.
  2. Intégrer un fichier swf contenant les éléments audio et contrôler la lecture via JavaScript. (Cela semble être la méthode utilisée par Google Les Paul Doodle .)
25
Nick

Vous pouvez désormais utiliser Web Audio API dans la plupart des navigateurs ( sauf IE et Opera Mini ) .

Essayez ce code:

// one context per document
var context = new (window.AudioContext || window.webkitAudioContext)();
var osc = context.createOscillator(); // instantiate an oscillator
osc.type = 'sine'; // this is the default - also square, sawtooth, triangle
osc.frequency.value = 440; // Hz
osc.connect(context.destination); // connect it to the destination
osc.start(); // start the oscillator
osc.stop(context.currentTime + 2); // stop 2 seconds after the current time

Si vous souhaitez réduire le volume, vous pouvez faire quelque chose comme ceci:

var context = new webkitAudioContext();
var osc = context.createOscillator();
var vol = context.createGain();

vol.gain.value = 0.1; // from 0 to 1, 1 full volume, 0 is muted
osc.connect(vol); // connect osc to vol
vol.connect(context.destination); // connect vol to context destination
osc.start(context.currentTime + 3); // start it three seconds from now

J'ai tiré la majeure partie de cela de l'expérimentation du chrome en lisant le Web Audio API Working Draft , que j'ai trouvé sur le lien de @brainjam.

J'espère que ça aide. Enfin, il est très utile d'inspecter les différents objets dans l'inspecteur chrome (ctrl-shift-i).

53
snapfractalpop

L'API Web Audio arrive sur Chrome. Voir http://googlechrome.github.io/web-audio-samples/samples/audio/index.html

Suivez les instructions de la section "Mise en route", puis découvrez les démos très impressionnantes.

Mise à jour (2017): c'est maintenant une interface beaucoup plus mature. L'API est documentée à https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API

11
brainjam

Sûr! Vous pouvez utiliser le synthétiseur de sons dans cette démo:

enter image description here

audioCtx = new(window.AudioContext || window.webkitAudioContext)();

show();

function show() {
  frequency = document.getElementById("fIn").value;
  document.getElementById("fOut").innerHTML = frequency + ' Hz';

  switch (document.getElementById("tIn").value * 1) {
    case 0: type = 'sine'; break;
    case 1: type = 'square'; break;
    case 2: type = 'sawtooth'; break;
    case 3: type = 'triangle'; break;
  }
  document.getElementById("tOut").innerHTML = type;

  volume = document.getElementById("vIn").value / 100;
  document.getElementById("vOut").innerHTML = volume;

  duration = document.getElementById("dIn").value;
  document.getElementById("dOut").innerHTML = duration + ' ms';
}

function beep() {
  var oscillator = audioCtx.createOscillator();
  var gainNode = audioCtx.createGain();

  oscillator.connect(gainNode);
  gainNode.connect(audioCtx.destination);

  gainNode.gain.value = volume;
  oscillator.frequency.value = frequency;
  oscillator.type = type;

  oscillator.start();

  setTimeout(
    function() {
      oscillator.stop();
    },
    duration
  );
};
frequency
<input type="range" id="fIn" min="40" max="6000" oninput="show()" />
<span id="fOut"></span><br>
type
<input type="range" id="tIn" min="0" max="3" oninput="show()" />
<span id="tOut"></span><br>
volume
<input type="range" id="vIn" min="0" max="100" oninput="show()" />
<span id="vOut"></span><br>
duration
<input type="range" id="dIn" min="1" max="5000" oninput="show()" />
<span id="dOut"></span>
<br>
<button onclick='beep();'>Play</button>

S'amuser!

J'ai obtenu la solution de Houshalter ici: Comment puis-je faire un bip Javascript?

Vous pouvez cloner et modifier le code ici: Démo du synthétiseur de tonalité sur JS Bin

Navigateurs compatibles:

  • Chrome pour mobile et ordinateur de bureau
  • Firefox mobile & desktop Opera mobile, mini & desktop
  • Navigateur Android
  • Navigateur Microsoft Edge
  • Safari sur iPhone ou iPad

Pas compatible

  • Internet Explorer version 11 (mais fonctionne sur le navigateur Edge)
8
CaptureWiz

Vous pouvez générer un fichier wav-e à la volée et le lire ( src )

// Legend
// DUR - duration in seconds   SPS - sample per second (default 44100)
// NCH - number of channels    BPS - bytes per sample

// t - is number from range [0, DUR), return number in range [0, 1]
function getSampleAt(t,DUR,SPS)
{
    return Math.sin(6000*t); 
}

function genWAVUrl(fun, DUR=1, NCH=1, SPS=44100, BPS=1) {
  let size = DUR*NCH*SPS*BPS; 
  let put = (n,l=4) => [(n<<24),(n<<16),(n<<8),n].filter((x,i)=>i<l).map(x=> String.fromCharCode(x>>>24)).join('');
  let p = (...a) => a.map( b=> put(...[b].flat()) ).join(''); 
  let data = `RIFF${put(44+size)}WAVEfmt ${p(16,[1,2],[NCH,2],SPS,NCH*BPS*SPS,[NCH*BPS,2],[BPS*8,2])}data${put(size)}`
  
  for (let i = 0; i < DUR*SPS; i++) {
    let f= Math.min(Math.max(fun(i/SPS,DUR,SPS),0),1);
    data += put(Math.floor( f * (2**(BPS*8)-1)), BPS);
  }
  
  return "data:Audio/WAV;base64," + btoa(data);
}


var WAV = new Audio( genWAVUrl(getSampleAt,5) ); // 5s
WAV.setAttribute("controls", "controls");
document.body.appendChild(WAV);
//WAV.play()

Voici la visualisation

function getSampleAt(t,DUR,SPS)
{
    return 0.5+Math.sin(15*t)/(1+t*t); 
}


// ----------------------------------------------

function genWAVUrl(fun, DUR=1, NCH=1, SPS=44100, BPS=1) {
  let size = DUR*NCH*SPS*BPS; 
  let put = (n,l=4) => [(n<<24),(n<<16),(n<<8),n].filter((x,i)=>i<l).map(x=> String.fromCharCode(x>>>24)).join('');
  let p = (...a) => a.map( b=> put(...[b].flat()) ).join(''); 
  let data = `RIFF${put(44+size)}WAVEfmt ${p(16,[1,2],[NCH,2],SPS,NCH*BPS*SPS,[NCH*BPS,2],[BPS*8,2])}data${put(size)}`
  
  for (let i = 0; i < DUR*SPS; i++) {
    let f= Math.min(Math.max(fun(i/SPS,DUR,SPS),0),1);
    data += put(Math.floor( f * (2**(BPS*8)-1)), BPS);
  }
  
  return "data:Audio/WAV;base64," + btoa(data);
}

function draw(fun, DUR=1, NCH=1, SPS=44100, BPS=1) {
  time.innerHTML=DUR+'s';
  time.setAttribute('x',DUR-0.3);
  svgCh.setAttribute('viewBox',`0 0 ${DUR} 1`);
  let p='', n=100; // n how many points to ommit
  for (let i = 0; i < DUR*SPS/n; i++) p+= ` ${DUR*(n*i/SPS)/DUR}, ${1-fun(n*i/SPS, DUR,SPS)}`;
  chart.setAttribute('points', p);
}

function frame() {
  let t=WAV.currentTime;
  point.setAttribute('cx',t)
  point.setAttribute('cy',1-getSampleAt(t))
  window.requestAnimationFrame(frame);
}

function changeStart(e) {
  var r = e.target.getBoundingClientRect();
  var x = e.clientX - r.left;
  WAV.currentTime = dur*x/r.width;
  WAV.play()
}

var dur=5; // seconds 
var WAV = new Audio(genWAVUrl(getSampleAt,dur));
draw(getSampleAt,dur);
frame();
.chart { border: 1px dashed #ccc; }
.axis { font-size: 0.2px}
audio { outline: none; }
Click at blue line (make volume to max):
<svg class="chart" id="svgCh" onclick="changeStart(event)">    
  <circle cx="0" cy="-1" r="0.05" style="fill: rgba(255,0,0,1)" id="point"></circle>
  <polyline id="chart" fill="none" stroke="#0074d9" stroke-width="0.01" points=""/>
  <text x="0.03" y="0.9" class="axis">0</text>
  <text x="0.03" y="0.2" class="axis">1</text>
  <text x="4.8" y="0.9" class="axis" id="time"></text>
</svg><br>
2

Ce n'est pas une vraie réponse à votre question car vous avez demandé une solution JavaScript, mais vous pouvez utiliser ActionScript. Il devrait fonctionner sur tous les principaux navigateurs.

Vous pouvez appeler des fonctions ActionScript à partir de JavaScript.

De cette façon, vous pouvez envelopper les fonctions de génération de sons ActionScript et en faire une implémentation JavaScript. Utilisez simplement Adobe Flex pour créer un petit swf, puis utilisez-le comme backend pour votre code JavaScript.

1
Mara Maya

C'est ce que j'ai recherché pour toujours et à la fin j'ai réussi à le faire moi-même comme je le voulais. Peut-être que vous l'aimerez aussi. Curseur simple avec fréquence et Push on/off:

buttonClickResult = function () {
        var button = document.getElementById('btn1');

        button.onclick = function buttonClicked()  {

                if(button.className=="off")  {
                        button.className="on";
                        oscOn ();
                }

                else if(button.className=="on")  {
                        button.className="off";
                        oscillator.disconnect();
                }
        }
};

buttonClickResult();

var oscOn = function(){

        window.AudioContext = window.AudioContext || window.webkitAudioContext;
        var context = new AudioContext();
        var gainNode = context.createGain ? context.createGain() : context.createGainNode();

        //context = new window.AudioContext();
        oscillator = context.createOscillator(),
                        oscillator.type ='sine';

        oscillator.frequency.value = document.getElementById("fIn").value;
        //gainNode = createGainNode();
        oscillator.connect(gainNode);
        gainNode.connect(context.destination);
        gainNode.gain.value = 1;
        oscillator.start(0);
};
<p class="texts">Frekvence [Hz]</p>
<input type="range" id="fIn" min="20" max="20000" step="100" value="1234" oninput="show()" />
<span id="fOut"></span><br>
<input class="off" type="button" id="btn1" value="Start / Stop" />
0
Xenon