Skip to content

Instantly share code, notes, and snippets.

@alexmacy
Last active June 9, 2017 17:35
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save alexmacy/5799c3e1f35f6aa0fbf5d723b7d6d35e to your computer and use it in GitHub Desktop.
Save alexmacy/5799c3e1f35f6aa0fbf5d723b7d6d35e to your computer and use it in GitHub Desktop.
Wave Phasing
license: mit

The first two drop-downs change the wave combination. The third drop-down adjusts the phase.

<!DOCTYPE html>
<html>
<head>
<style>
body {
margin: 0px;
overflow:hidden;
}
div {
margin-left: 10px;
}
path {
fill: none;
stroke: black;
stroke-width: 1;
}
svg {
border: 1px solid steelblue;
margin: 10px;
}
</style>
<script src="//d3js.org/d3.v4.min.js"></script>
</head>
<body>
<svg></svg>
<div>
<select id="wave-select1" class="wave-select"></select>
<select id="wave-select2" class="wave-select"></select>
<select id="speed-select"></select>
<button id="stop-start" onclick="playing ? stopSound() : phase()">start</button>
Mute: <input type="checkbox" onchange="muteSound()" checked/>
</div>
</body>
<script>
var width = Math.max(940, innerWidth-20),
height = Math.max(450, innerHeight-60);
var playing = false;
var source;
var timer;
var audioCtx = new (window.AudioContext || window.webkitAudioContext)(),
gainNode = audioCtx.createGain(),
sampleRate = 44100,
frequency = 440,
waveCycles = 4,
samples = sampleRate / frequency;
var eases = [
"easeLinear",
"easePolyIn", "easePolyOut", "easePolyInOut",
"easeQuadIn", "easeQuadOut", "easeQuadInOut",
"easeCubicIn", "easeCubicOut", "easeCubicInOut",
"easeSinIn", "easeSinOut", "easeSinInOut",
"easeExpIn", "easeExpOut", "easeExpInOut",
"easeCircleIn", "easeCircleOut", "easeCircleInOut",
"easeElasticIn", "easeElasticOut", "easeElasticInOut",
"easeBackIn", "easeBackOut", "easeBackInOut",
"easeBounceIn", "easeBounceOut", "easeBounceInOut"
];
d3.selectAll(".wave-select").selectAll("option")
.data(eases)
.enter().append("option")
.attr("value", function (d) {return d})
.text(function (d) {return d});
d3.select("#speed-select").selectAll("option")
.data(d3.range(1, 20))
.enter().append("option")
.attr("value", function (d) {return d})
.text(function (d) {return d});
d3.selectAll("select")
.on("change", function () {if (playing) phase()});
var buffer = audioCtx.createBuffer(2, samples * waveCycles, sampleRate);
var svg = d3.select("svg")
.attr("width", width)
.attr("height", height);
var x = d3.scaleLinear()
.range([0, width])
.domain([samples * .25, buffer.length - samples * .75]);
var y = d3.scaleLinear()
.range([height - 60, 60])
.domain([-1, 1]);
var wave = d3.line()
.curve(d3.curveMonotoneX)
.x(function (d, i) {return x(i)})
.y(function (d) {return y(d)});
var waveShape1 = svg.append("path")
.datum([])
.attr("stroke-dasharray", [5, 5])
.attr("stroke", "#e9e9e9")
.attr("d", wave);
var waveShape2 = svg.append("path")
.datum([])
.attr("stroke-dasharray", [5, 5])
.attr("stroke", "#e9e9e9")
.attr("d", wave);
var mainWave = svg.append("path")
.datum([])
.style("stroke", "red")
.style("stroke-width", 2)
.attr("d", wave);
var position = svg.append("circle")
.attr("r", 5);
phase();
function loadAudio() {
var offset = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0;
var ease1 = d3.select("#wave-select1").property("value");
var ease2 = d3.select("#wave-select2").property("value");
var combined = new Float32Array(buffer.length),
buffer1 = new Float32Array(buffer.length),
buffer2 = new Float32Array(buffer.length);
for (i = 0; i < buffer.length; i++) {
var thisVal = Math.abs((i + samples / 4) % samples / (samples / 2) - 1),
thisVal2 = Math.abs((i + offset + samples / 4) % samples / (samples / 2) - 1);
var eased = (d3[ease1](thisVal) - .5) * 2,
eased2 = (d3[ease2](thisVal2) - .5) * 2;
buffer.getChannelData(1)[i] = eased;
buffer.getChannelData(0)[i] = eased2;
}
combined = combined.map(function (d, i) {
return (buffer.getChannelData(0)[i] + buffer.getChannelData(1)[i]) / 2;
});
waveShape1.datum(buffer.getChannelData(0)).attr("d", wave);
waveShape2.datum(buffer.getChannelData(1)).attr("d", wave);
mainWave.datum(combined).attr("d", wave);
position.attr("cx", function (d) {return x(buffer.length >> 1)})
.attr("cy", function (d) {return y(combined[buffer.length >> 1])});
if (playing == false) playSound();
}
function phase() {
var t = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0;
if (playing) stopSound();
d3.select("#stop-start").text("stop");
var step = d3.select("#speed-select").property("value");
timer = d3.timer(function () {
t = t >= samples ? 0 : t;
loadAudio(t += +step);
}, 1);
}
function playSound() {
if (playing) stopSound();
playing = true;
source = audioCtx.createBufferSource();
source.buffer = buffer;
source.connect(gainNode);
gainNode.connect(audioCtx.destination);
source.loop = true;
muteSound();
source.start();
}
function stopSound() {
timer.stop();
playing = false;
if (source) source.stop();
muteSound();
d3.select("#stop-start").text("start");
}
function muteSound() {
return gainNode.gain.value = d3.select("input").property("checked") ? 0 : 1;
}
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment