Skip to content

Instantly share code, notes, and snippets.

@quezo
Created March 2, 2021 05:59
Show Gist options
  • Save quezo/06010d48985d849c0e78ff41fd711626 to your computer and use it in GitHub Desktop.
Save quezo/06010d48985d849c0e78ff41fd711626 to your computer and use it in GitHub Desktop.
Makes a lot of Noise by Default
<div class="gradientOverlay"></div>
<div id="container">
<canvas id="canvas"></canvas>
<div class="params">
<div class="controls">
<input type="radio" name="wave" value="0" id="w0" checked onchange="setWave(this.value)"onclick="setWave(this.value)"><label for="w0">sine</label><br/>
<input type="radio" name="wave" value="1" id="w1" onchange="setWave(this.value)"onclick="setWave(this.value)"><label for="w1">square</label><br/>
<input type="radio" name="wave" value="2" id="w2" onchange="setWave(this.value)"onclick="setWave(this.value)"><label for="w2">sawtooth</label><br/>
<input type="radio" name="wave" value="3" id="w3" onchange="setWave(this.value)"onclick="setWave(this.value)"><label for="w3">triangle</label>
</div>
<div class="controls">
<input type="checkbox" value="0" id="p0" checked onchange="startStop(this.value)"><br/>
<input type="checkbox" value="1" id="p1" checked onchange="startStop(this.value)"><br/>
<input type="checkbox" value="2" id="p2" checked onchange="startStop(this.value)"><br/>
<input type="checkbox" value="3" id="p3" checked onchange="startStop(this.value)">
</div>
<div class="controls" style="width:50%;">
frequency
<br/>
<input type="range" id="frequencyInput" name="frequencyInput" min="1" max="440" value="1" step ="1" onchange="setFrequency(this)" style="width:90%">
<label id="frequencyRate"></label>
<br/>
detune
<br/>
<input type="range" id="detuneInput" name="detuneInput" min="-5000" max="5000" value="0" step="0.01" onchange="setDetune(this)" style="width:90%">
<label id="detuneRate"></label>
</div>
<div class="controls">
gain
<br>
<input type="range" id="gainInput" name="gainInput" min="0.00" max="1" value=".5" step="0.01" onchange="setGain(this)" onclick="setGain(this)">
<label id="gainRate"></label>
<br/>
<button id="reset" value="reset" onmousedown="reset()">reset</button>
<button id="randomize" value="randomize" onmousedown="randomize()">randomize</button>
</div>
</div>
</div>
<div class="blablah">
<h1>oscillator study</h1>
<div style="width: 250px; text-align: justify;">
<p>
testing the <a href="http://www.w3.org/TR/webaudio/">Web Audio API</a>
so this works on <a href="http://caniuse.com/#search=web%20audio%20api">a restricted set</a> of browsers. you should see the animations on recent browsers though (works on IE11, Chrome, FF, Opera & Safari)
</p>
<p>
the curves correspond to the default periodic functions available in the API.
more info and the formulas here: <a href="http://en.wikibooks.org/wiki/Sound_Synthesis_Theory/Oscillators_and_Wavetables">sound synthesis article on wikipedia</a>
</p>
<p>
use <input type="radio" id="r_gizmo"><label for="r_gizmo"> select this waveform</label><br/>
use <input type="checkbox" id="c_gizmo" checked ><label for="c_gizmo"> mute / unmute this waveform</label><br/>
</p>
<p>
you've got to love 8bit music to stay here for more than 10 seconds :)
</p>
</div>
</div>
/**
* Created by nico on 25/01/14.
*/
var actx,
c = document.getElementById('canvas');
ctx = c.getContext('2d'),
size = c.width = Math.min( window.innerWidth, window.innerHeight ),
c.height = size;
waves = [],
PI = Math.PI,
PI2 = PI * 2;
var raf = window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.msRequestAnimationFrame ||
window.oRequestAnimationFrame ||
function(func) { setTimeout( func, 1000 / 60 ); };
var selectedWave;
var gain;
//background noise
var bg = document.createElement("canvas");
bg.width = bg.height = 128;
var bg_ctx = bg.getContext( "2d" );
bg_ctx.fillStyle = "#f00";
bg_ctx.fillRect(0,0,100,100);
var img = bg_ctx.getImageData(0,0,128,128);
var data = img.data;
for( var i = 0; i < data.length; i += 4 )
{
var val = 0xCC + ( parseInt( Math.random() * 0x33 ));
data[ i ] = data[ i + 1 ] = data[ i + 2 ] = val;
data[ i + 3 ] = 255;
}
bg_ctx.putImageData( img,0,0 );
document.body.style.backgroundImage = "url(" + bg.toDataURL("image/png")+ ")";
/////////////////////////////////
var RGB = function( r,g,b )
{
this.r = r;
this.g = g;
this.b = b;
//from http://www.javascripter.net/faq/rgbtohex.htm
this.toHexString = function()
{
return this.toHex( this.r ) + this.toHex( this.g ) + this.toHex( this.b );
}
this.toHex = function toHex(n)
{
n = parseInt( n, 10 );
if ( n == 0 || isNaN( n )) return "00";
n = Math.max( 0, Math.min( n, 255 ) );
return "0123456789ABCDEF".charAt( ( n - n % 16 ) / 16 ) + "0123456789ABCDEF".charAt( n % 16 );
}
}
var Wave = function( frequency, detune, gain, method )
{
this.x = 0;
this.y = 0;
this.dest =
{
frequency: frequency,
detune: detune,
gain: gain
};
this.color = null;
this.playing = false;
this.process = null;
this.method = method || 0;
switch( method )
{
default:
case Wave.SINE:
this.process = function ( n ){ return Math.sin( n * PI2 ); };
this.color = new RGB( 255, 64,0 );
break;
case Wave.SQUARE:
this.process = function ( n ){ return Math.sin( n * PI2 ) > 0 ? 1 : -1; };
this.color = new RGB( 0, 99, 204 );
break;
case Wave.SAWTOOTH:
this.process = function ( n ){ return ( n - Math.floor( n +.5 ) ) * 2; };
this.color = new RGB( 255, 204, 0 );
break;
case Wave.TRIANGLE:
this.process = function ( n ){ return ( 1 - Math.abs( n - Math.floor( n + .5 ) ) * 4 ); };
this.color = new RGB( 0, 255, 102 );
break;
case Wave.NOISE:
this.process = function ( n ){ return ( ( Math.random() -.5 ) * 2 ); };
this.color = new RGB( 0, 102, 153 );
break;
}
this.render = function( normalTime, ctx )
{
var t = normalTime;
var max = t + 1;
var step = 1 / 250;
var m = Math.sqrt( this.gain.gain.value );
var radiusX = .45 + .35 * ( 1 - m );
var radiusY = .12 * m;
ctx.beginPath();
ctx.strokeStyle = "rgba( "+ this.color.r +","+ this.color.g +","+ this.color.b +","+ 1 +")";
ctx.fillStyle = "rgba( "+ this.color.r +","+ this.color.g +","+ this.color.b +","+ this.gain.gain.value * .5 +")"
for( var t = normalTime; t < max; t += step )
{
this.x = Math.cos( t * PI2 ) * radiusX;
this.y = ( -.5 + this.method * ( 3 * .125 ) ) + ( this.process( ( normalTime + t ) * this.osc.frequency.value ) + Math.sin( t * PI2 ) ) * radiusY;
ctx.lineTo( this.x, this.y );
}
ctx.closePath();
ctx.fill();
ctx.stroke();
for( t = 0; t < 1; t += .25 )
{
this.x = Math.cos( t * PI2 ) * radiusX;
this.y = ( -.5 + this.method * ( 3 *.125 ) ) + ( this.process( ( normalTime + t ) * this.osc.frequency.value ) + Math.sin( t * PI2 ) ) * radiusY;
ctx.beginPath();
ctx.arc( this.x, this.y, 0.01, 0, PI2 );
ctx.stroke();
}
}
//create the oscillator and gain objects
this.rebuildOSC = function()
{
if( actx )
{
if( this.gain == null )
{
this.gain = actx.createGain();
this.gain.connect( actx.destination );
this.gain.gain.value = gain;
}
else
{
this.osc.disconnect();
}
this.osc = actx.createOscillator();
this.osc.connect( this.gain );
this.osc.type = method;
this.osc.frequency.value = frequency;
this.osc.detune.value = detune;
this.osc.start( 0 );
this.playing = true;
}
//generic object to handle graphics only (unicorn-mode)
else
{
this.osc = {
frequency: { value: frequency },
detune: { value: frequency },
start : function(value){},
stop : function(value){}
};
this.gain = {
gain: { value:gain }
};
}
}
this.rebuildOSC();
}
Wave.SINE = 0;
Wave.SQUARE = 1;
Wave.SAWTOOTH = 2;
Wave.TRIANGLE = 3;
/////////////////////////////////
function update()
{
ctx.restore();
size = c.width = Math.min( window.innerWidth, window.innerHeight ),
c.height = size;
container.style.width = size + "px";
var time = Date.now() * 0.0001;
ctx.clearRect( 0,0, c.width, c.height );
//normalized unit
var nu = 1 / size;
//normalized time : -1 >= NT >= 1
normalTime = -1 + ( time % 1 ) * 2;
ctx.save();
ctx.scale( c.width / 2 , c.height / 2 );
ctx.translate( 1 + nu, 1 + nu );
ctx.lineCap = "round";
ctx.lineWidth = nu * 4;
for( var i =0; i < waves.length; i++)
{
var w = waves[ i ];
w.render( normalTime, ctx );
w.osc.frequency.value += ( w.dest.frequency - w.osc.frequency.value ) *.1;
w.osc.detune.value += ( w.dest.detune - w.osc.detune.value ) *.1;
w.gain.gain.value += ( w.dest.gain - w.gain.gain.value ) *.1;
}
updateSettings();
raf( update );
}
function init()
{
actx = window['AudioContext'] ? new AudioContext() : window['webkitAudioContext'] ? new webkitAudioContext() : null;
if( !actx )
{
console.log( 'NOOooooooooOOOOooooOOOOOHH !! ! !\n couldn\'t find the AUDIO CONTEXT....\n QUICK ! \n' );
console.log( " / \n .7 \n \ , // \n |\.--._/|// \n /\ ) ) ).'/ \n /( \ // / \n /( J`((_/ \ \n / ) | _\ / \n /|) \ eJ L \n | \ L \ L L \n / \ J `. J L \n | ) L \/ \ \n / \ J (\ / \n _....___ | \ \ \``` \n ,.._.-' '''--...-||\ -. \ \ \n .'.=.' ` `.\ [ Y \n / / \] J \n Y / Y Y L \n | | | \ | L \n | | | Y A J \n | I | /I\ / \n | \ I \ ( |]/| \n J \ /._ / -tI/ | \n L ) / /'-------'J `'-:. \n J .' ,' ,' , \ `'-.__ \ \n \ T ,' ,' )\ /| ';'---7 / \n \| ,'L Y...-' / _.' / \ / / \n J Y | J .'-' / ,--.( / \n L | J L -' .' / | /\ \n | J. L J .-;.-/ | \ .' / \n J L`-J L____,.-'` | _.-' | \n L J L J `` J | \n J L | L J | \n L J L \ L \ \n | L ) _.'\ ) _.'\ \n L \('` \ ('` \ \n ) _.'\`-....' `-....' \n ('` \ \n `-.___/ sk ");
console.log( " UNICORN FALLBACK!!!\n\n\n ");
}
var container = document.getElementById( "container" );
container.style.width = size + "px";
ctx.fillStyle = "#FFF";
ctx.fillRect( 0,0,size, size );
waves.push( new Wave( 12, 2393, 1, Wave.SINE ) );
waves.push( new Wave( 4, 0, .65, Wave.SQUARE ) );
waves.push( new Wave( 1, 0, .25, Wave.SAWTOOTH ) );
waves.push( new Wave( 240, 0, .3, Wave.TRIANGLE ) );
setWave( 0 );
}
/////////////////////////////////////////
// UI
/////////////////////////////////////////
function setWave( value )
{
selectedWave = waves[ parseInt( value ) ];
updateSettings();
}
function setFrequency( element )
{
selectedWave.osc.frequency.value = selectedWave.dest.frequency = element.value;
updateSettings();
}
function setDetune( element )
{
selectedWave.osc.detune.value = selectedWave.dest.detune = element.value;
updateSettings();
}
function setGain( element )
{
selectedWave.gain.gain.value = selectedWave.dest.gain = element.value;
updateSettings();
}
function startStop( value )
{
//alternative : rebuild the oscillator
//they will run out of sync
if( waves[ value ].playing )
{
// waves[ value ].osc.stop(0);
waves[ value ].dest.gain = 0;
waves[ value ].playing = false;
}
else
{
// waves[ value ].rebuildOSC();
waves[ value ].dest.gain = 1;
waves[ value ].playing = true;
}
setWave( value );
}
function reset()
{
waves.forEach( function( w )
{
selectedWave = w;
selectedWave.osc.frequency.value = selectedWave.dest.frequency =1;
selectedWave.osc.detune.value = selectedWave.dest.detune =0;
selectedWave.gain.gain.value = selectedWave.dest.gain =1;
updateSettings();
});
setWave( 0 );
}
function randomize()
{
waves.forEach( function( w )
{
if( w.playing )
{
w.dest.frequency = Math.max( 1, Math.min( w.dest.frequency + parseInt( ( Math.random() -.5 ) * 10 ), 440 ) );
w.dest.detune = Math.max( -5000, Math.min( w.dest.detune + parseInt( ( Math.random() -.5 ) * 20 ), 5000 ) );
w.dest.gain += ( Math.random() -.5 ) * .1;
}
});
}
function updateSettings()
{
document.getElementById( "frequencyInput").value = selectedWave.osc.frequency.value;
document.getElementById( "frequencyRate").innerHTML = ""+ parseInt( selectedWave.osc.frequency.value, 10 );
document.getElementById( "detuneInput").value = selectedWave.osc.detune.value;
document.getElementById( "detuneRate").innerHTML = ""+ parseInt( selectedWave.osc.detune.value, 10 );
document.getElementById( "gainInput").value = selectedWave.gain.gain.value;
document.getElementById( "gainRate").innerHTML = ""+ parseFloat( selectedWave.gain.gain.value).toFixed( 2 );
var radio = document.getElementsByName( "wave" );
for( var i =0; i< radio.length; i++ )
{
radio.item( i ).checked = radio.item( i ).value == selectedWave.method;
}
};
/////////////////////////////////////////
// GO UNICORN GO !
/////////////////////////////////////////
init();
raf( update );
html, body
{
width:100%;
height:100%;
overflow:hidden;
background-color: #303030;
margin : 0;
}
.gradientOverlay
{
background: -moz-linear-gradient(left, rgba(255,255,255,0.01) 0%, rgba(255,255,255,0.01) 1%, rgba(255,255,255,1) 35%, rgba(255,255,255,1) 65%, rgba(255,255,255,0) 100%); /* FF3.6+ */
background: -webkit-gradient(linear, left top, right top, color-stop(0%,rgba(255,255,255,0.01)), color-stop(1%,rgba(255,255,255,0.01)), color-stop(35%,rgba(255,255,255,1)), color-stop(65%,rgba(255,255,255,1)), color-stop(100%,rgba(255,255,255,0))); /* Chrome,Safari4+ */
background: -webkit-linear-gradient(left, rgba(255,255,255,0.01) 0%,rgba(255,255,255,0.01) 1%,rgba(255,255,255,1) 35%,rgba(255,255,255,1) 65%,rgba(255,255,255,0) 100%); /* Chrome10+,Safari5.1+ */
background: -o-linear-gradient(left, rgba(255,255,255,0.01) 0%,rgba(255,255,255,0.01) 1%,rgba(255,255,255,1) 35%,rgba(255,255,255,1) 65%,rgba(255,255,255,0) 100%); /* Opera 11.10+ */
background: -ms-linear-gradient(left, rgba(255,255,255,0.01) 0%,rgba(255,255,255,0.01) 1%,rgba(255,255,255,1) 35%,rgba(255,255,255,1) 65%,rgba(255,255,255,0) 100%); /* IE10+ */
background: linear-gradient(to right, rgba(255,255,255,0.01) 0%,rgba(255,255,255,0.01) 1%,rgba(255,255,255,1) 35%,rgba(255,255,255,1) 65%,rgba(255,255,255,0) 100%); /* W3C */
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#03ffffff', endColorstr='#00ffffff',GradientType=1 ); /* IE6-9 */
position: absolute;
width : 100%;
height : 100%;
}
.blablah
{
background-color: #FDFDFD;
padding: 2em;
font-family: "verdana";
font-size: .8em;
position: absolute;
left: 2em;
-webkit-box-shadow: 0 12px 32px -8px black;
-moz-box-shadow: 0 12px 32px -8px black;
box-shadow: 0 12px 32px -8px black;
}
h1
{
text-align: center;
font-family: "verdana";
font-size: 1.6em;
text-transform: uppercase;
}
#container
{
font-family: "verdana";
font-size: .7em;
display: block;
margin : auto;
position : absolute;
top : 0;
left : 0;
bottom : 0;
right : 0;
}
canvas
{
display: block;
position: absolute;
pointer-events:none;
}
.params
{
display: block;
position: absolute;
width: 100%;
bottom: 30px;
}
.controls
{
margin:auto;
display: block;
position: relative;
float: left;
margin-right: 1em;
}
#frequencyInput, #detuneInput, #gainInput
{
vertical-align: middle;
}
label
{
cursor:pointer;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment