Web Audio API programmeerimine

Olen tuntud selle poolest, et komponeerin vahest elektroonilist muusikat, nii umbes korra kuus võtan midagi ette ja kääksutan natuke tasuta tarkvara (Linux Multimedia System) kasutades. Ühe projektiga tekkis vajadus mikseri järele, mida mul endal ei ole ja pole kavatsust ka raha ja ruumi raisata omale stuudio ehitamisega. Oleks vaja lihtsalt virtuaalset mikserit, millega saab kahte rütmi omavahel kokku miksida. Sellist aga pole leidnud siiamaani. Võtsin kätte ja lugesin Web Audio API kohta ja tunnen nüüd, et oskan seda piisavalt hästi kasutada, et võtta ette mõni väike oma projekt.

Võtsin ülesande ette fookusega luua interaktiivne mikser, mis lisaks kahe audiofaili kokku miksimisele ka salvestab tulemuse, mida võib siis hiljem mõnes audio töötlemise programmis edasi töödelda. Postitan javascript koodi ja hiljem räägin mis seal toimub.

"use strict";

var audioContext = new AudioContext();
var audioBuffer;
var audioBuffer2;
var playSound;
var playSound2;
var gain;
var gain2;
var gainSum;
var chunks = [];
var dest = audioContext.createMediaStreamDestination();
var mediaRecorder = new MediaRecorder(dest.stream);

var getSound = new XMLHttpRequest();
var getSound2 = new XMLHttpRequest();
getSound.open("get", "sounds/beat1.wav", true);
getSound.responseType = "arraybuffer";
getSound2.open("get", "sounds/beat2.wav", true);
getSound2.responseType = "arraybuffer";

getSound.onload = function()
{
	audioContext.decodeAudioData(getSound.response, function(buffer)
	{
		audioBuffer = buffer;
	});
};

getSound2.onload = function()
{
	audioContext.decodeAudioData(getSound2.response, function(buffer)
	{
		audioBuffer2 = buffer;
	});
};
getSound.send();
getSound2.send();
function playback()
{
	playSound = audioContext.createBufferSource();
	playSound.buffer = audioBuffer;
	playSound2 = audioContext.createBufferSource();
	playSound2.buffer = audioBuffer2;
	gain = audioContext.createGain();
	gain2 = audioContext.createGain();
	gainSum = audioContext.createGain();
	playSound.connect(gain);
	playSound2.connect(gain2);
	gain.connect(gainSum);
	gain2.connect(gainSum);
	gainSum.connect(audioContext.destination);
	gainSum.connect(dest);
	playSound2.start(audioContext.currentTime);
	playSound.start(audioContext.currentTime);
	mediaRecorder.ondataavailable = function(evt) {
       // push each chunk (blobs) in an array
       chunks.push(evt.data);
     };
	mediaRecorder.onstop = function(evt) {
       // Make blob out of our blobs, and open it.
       var blob = new Blob(chunks, { 'type' : 'audio/ogg; codecs=opus' });
       document.querySelector("audio").src = URL.createObjectURL(blob);
     };
	mediaRecorder.start();
}
window.onload = function()
{
	var onOff = document.getElementById("on-off");
	var span = document.getElementsByTagName("span")[0];
	var mix = false;
	var mixerSliderVal = document.getElementsByTagName("input")[1].value;

	setInterval(function()
	{
		//0 to 100
		mixerSliderVal = document.getElementsByTagName("input")[1].value;
		if(gain)
		{
			gain.gain.setValueAtTime( mixerSliderVal / 100, audioContext.currentTime);
			gain2.gain.setValueAtTime(  (100 - mixerSliderVal ) / 100, audioContext.currentTime);
		}
	}, 50);
	onOff.addEventListener("click", function()
	{
		if(!mix)
		{
			mix = true;
			playback();
			onOff.value = "stop";
			span.innerHTML = "Click to stop.";
		}
		else
		{
			mix = false;
			onOff.value = "start";
			span.innerHTML = "Click to start.";
			playSound.stop();
			playSound2.stop();
			mediaRecorder.stop();
			chunks = [];
			dest = audioContext.createMediaStreamDestination();
			mediaRecorder = new MediaRecorder(dest.stream);
		}
	} );

};
<span 				data-mce-type="bookmark" 				id="mce_SELREST_start" 				data-mce-style="overflow:hidden;line-height:0" 				style="overflow:hidden;line-height:0" 			></span>

Effekt koosneb ühendatud tippudest, mis moodustab graafi.

mixerMindMap

Kõigepealt luuakse bufferSource tipp factory funktsiooniga audioContext.createBufferSource(). Samamoodi luuakse Gain tipud. Kõige viimane tipp on alati audioContext.destination, see tähendab põhimõtteliselt standard audio väljundit, milleks on tavaliselt kõlarid. Ühendades gainSum tipu ainult audioContext.destination-iga on võimalik audiot kuulata, lindistamiseks on vaja ka mediaStreamDestination tippu.

Nüüd mikseri funktsionaalsuse loomiseks on gain1 lihtsalt funktsioon f(x) = x ja gain2 on funktsioon f(x) = 100 – x . Kus   0 < x < 100 . Sada on antud kasutajaliidese liuguri ( slideri ) maksimaalne väärtus ja 0 on minimaalne. Lisaks on need y-i väärtused jagatud 100-ga, et saada väärtused vahemikus 0 – 1. See on näha koodis : gain.gain.setValueAtTime( mixerSliderVal / 100, audioContext.currentTime); . Gain väärtused määratakse mutaator funktsiooniga ( setter ), otseselt väärtuste muutmine on ebasoovitav ja tulevikus vist eemaldatakse.

Seega matemaatiliselt on mikseri loomine väga lihtne, rohkem tuleb vaeva näha sellega, et kõik graafi tipud oleksid õigel ajal omavahel ühenduses. Liugurilt teatud aja tagant väärtuste lugemiseks kasutatakse setInterval(function, time) funktsiooni.

Edasi html fail näeb välja selline.

<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8">
		<title>app</title>
		<script src="js/app.js"></script>
				<link rel="stylesheet" href="css/app.css">
	</head>
	<body>
<div id="header">
<H1>Mixer</H1>

 Html and css</div>
<div class="application">
<div class="osc-controls">
<form>
					<input id="on-off" type="button" value="start">
					<span>Click to start.</span>

 Use slider to mix.

					<input type="range">
				</form></div>
</div>
<audio controls></audio>
	</body>
</html>

Ja css.

body
{
	background-color:orange;
}
p, li, h1
{
	color:green;
}
#header
{
	background-color:rgb(150,100,150);
}

.osc-controls
{
	background-color:rgb(200,100,100);
	border-style:solid;
	border-color:rgb(90,50,50);
	border-width:2px;
	border-radius:10px;
}
.application
{
	width:350px;
	margin:0 auto;
}
div
{
	margin:20px;
	padding:5px;
}<span 				data-mce-type="bookmark" 				id="mce_SELREST_start" 				data-mce-style="overflow:hidden;line-height:0" 				style="overflow:hidden;line-height:0" 			></span>

Vastavad audiofailid võetakse XMLHttpRequest objekti kasutades serverist /sounds kataloogist. Seega see kood ei tööta ilma serverita. Ise kasutasin Sublime Text-i redaktori serverit.

Üldiselt on need koodijupid heaks malliks sarnaste interaktiivsete effektide loomiseks, kus tuleb reaalajas mingeid parameetreid muuta ja samal  ajal lindistada. Lindistamine töötab selliselt küll ainult Chrome ja Firefox veebilehitsejates, kuid on heaks alguspunktiks.

Advertisements