Skip to content

Instantly share code, notes, and snippets.

@sfpgmr
Last active October 18, 2015 07:08
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 sfpgmr/fa47f359604f0cfa3210 to your computer and use it in GitHub Desktop.
Save sfpgmr/fa47f359604f0cfa3210 to your computer and use it in GitHub Desktop.
Web Audio モジュラー接続画面デモ

Web Audio APIのモジュラー接続デモを作ってみる(1)

WebAudioのモジュラー接続デモですが、現時点ではノードをドラッグできるくらいで何もできません。機能は徐々に実装していく予定です。

Chrome,Edge,Firefoxで動作を確認しています。

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<link rel="stylesheet" type="text/css" href="style.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.6/d3.js" ></script>
<script type="text/javascript" src="./script.out.js" ></script>
<script>
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
ga('create', 'UA-15457703-11', 'auto');
ga('send', 'pageview');
</script>
</head>
<body>
</body>
</html>
{
"compilerOptions": {
"target": "ES6"
}
}
"use strict";
const nodeHeight = 200;
const nodeWidth = 100;
const pointSize = 16;
var svg;
var counter = 0;
var audioNodes = [
];
var audioConnections = [
];
class NodeViewData {
constructor(x, y) {
this.x = x | 0;
this.y = y | 0;
}
}
class AudioParam_ extends NodeViewData {
constructor(name, param) {
super();
this.id = counter++;
this.name = name;
this.audioParam = param;
}
}
class AudioNode_ extends NodeViewData {
constructor(audioNode) {
super();
this.id = counter++;
this.audioNode = audioNode;
this.name = audioNode.constructor.toString().match(/function\s(.*)\(/)[1];
this.audioParams = [];
for (var i in audioNode) {
if (typeof audioNode[i] === 'function') {
this[i] = audioNode[i].bind(audioNode);
} else {
if (typeof audioNode[i] === 'object') {
this[i] = audioNode[i];
if (audioNode[i] instanceof AudioParam) {
this.audioParams.push(new AudioParam_(i, audioNode[i]));
}
} else {
Object.defineProperty(this, i, {
get: ((i) => this.audioNode[i]).bind(null, i),
set: ((i, v) => { this.audioNode[i] = v; }).bind(null, i),
enumerable: true,
configurable: false
});
}
}
}
}
// 1つだけだとノードの削除で2つの場合はコネクションの削除
static remove(node, to) {
if (!to) {
for (var i = 0; i < AudioNode_.audioNodes.length; ++i) {
if (AudioNode_.audioNodes[i] === node) {
AudioNode_.audioNodes.splice(i--, 1);
}
}
for (var i = 0; i < AudioNode_.audioConnections.length; ++i) {
var n = AudioNode_.audioConnections[i];
if (n.from === node || n.to === node) {
AudioNode_.audioConnections.splice(i--, 1);
} else {
if (!n.to.param) {
for (var d in node.audioParams) {
if (d === n.to.param) {
AudioNode_.audioConnections.splice(i--, 1);
}
};
}
}
}
} else {
for (var i = 0; i < AudioNode_.audioConnections.length; ++i) {
let n = AudioNode_.audioConnections[i];
if (!to.param) {
if (n.from === node && n.to === to) {
AudioNode_.audioConnections.splice(i--, 1);
}
} else {
if (n.from === node && n.to.param === to.param && n.to.node === to.node) {
AudioNode_.audioConnections.splice(i--, 1);
}
}
}
}
}
static create(audionode) {
var obj = new AudioNode_(audionode);
AudioNode_.audioNodes.push(obj);
return obj;
}
static connect(from_, to_) {
// 存在チェック
for (var i = 0, l = AudioNode_.audioConnections.length; i < l; ++i) {
if (AudioNode_.audioConnections[i].from === from_
&& AudioNode_.audioConnections[i].to === to_) {
throw (new Error('接続が重複しています。'));
}
}
if (from_ instanceof AudioNode_) {
if (to_ instanceof AudioNode_) {
from_.connect(to_.audioNode);
} else {
if (to_.param instanceof AudioParam_) {
from_.connect(to_.param.audioParam);
} else {
from_.connect(to_.node, null, to_.param);
}
}
} else if (from_.param) {
if (to_ instanceof AudioNode_) {
from_.node.connect(to_.audioNode, from_.param);
} else {
if (to_.param instanceof AudioParam_) {
from_.node.connect(to_.param.audioParam, from_.param);
} else {
from_.node.connect(to_.node, from_.param, to_.param);
}
}
} else {
throw new Error('Connection Error');
}
AudioNode_.audioConnections.push
({
'from': from_,
'to': to_
});
}
}
AudioNode_.audioNodes = [];
AudioNode_.audioConnections = [];
var nodeGroup, lineGroup;
var drag;
window.onload = () => {
var ctx = new AudioContext();
d3.select(window)
.on('resize',function(){
if(svg){
svg.attr({
width:window.innerWidth,
height:window.innerHeight
})
}
});
// for(var i in ctx){
// if(i.match(/create/)){
// try {
// var o = ctx[i]();
// for(var j in o){
// if(o[j] instanceof AudioParam){
// for(var k in o[j]){
// console.log(' ',k,o[j][k] instanceof AudioParam,o[j][k]);
// }
// }
// }
//
// } catch (e){
// console.log(e);
// }
//
// }
// }
//
var osc = AudioNode_.create(ctx.createOscillator());
osc.x = 100;
osc.y = 200;
var gain = AudioNode_.create(ctx.createGain());
gain.x = 400;
gain.y = 200;
var filter = AudioNode_.create(ctx.createBiquadFilter());
filter.x = 250;
filter.y = 330;
var out = AudioNode_.create(ctx.destination);
out.x = 550;
out.y = 300;
// for(var i in osc){
// console.log(i + ':' + osc[i]);
// for(var j in osc[i]){
// console.log(' ' + j + ':' + osc[i][j]);
// }
// }
AudioNode_.connect(osc, filter);
AudioNode_.connect(osc,{node:gain,param:gain.audioParams[0]});
AudioNode_.connect(filter,gain);
AudioNode_.connect(gain,out);
console.log(AudioNode_.audioNodes.length);
console.log(AudioNode_.audioConnections.length);
console.log(AudioNode_.audioConnections[0]);
// osc.audioNode.type = 'sawtooth';
osc.type = 'sawtooth';
console.log(osc.type);
osc.frequency.value = 440;
// osc.start();
// osc.stop(ctx.currentTime + 0.1);
drag = d3.behavior.drag()
.origin(function (d) { return d; })
.on("drag", function (d) {
if (d3.event.sourceEvent.shiftKey) {
console.log('aa');
return;
}
d.x += d3.event.dx;
d.y += d3.event.dy;
d3.select(this).attr('transform', 'translate(' + (d.x - nodeWidth / 2) + ',' + (d.y - nodeHeight / 2) + ')');
draw();
});
svg = d3.select('body')
.append('svg')
.attr({ 'width': window.innerWidth, 'height': window.innerHeight });
nodeGroup = svg.append('g');
lineGroup = svg.append('g');
draw();
// window.setTimeout(() => {
// //AudioNode_.remove(osc);
// //AudioNode_.remove(out);
// console.log(AudioNode_.audioNodes.length);
// console.log(AudioNode_.audioConnections.length);
// draw();
// }, 2000);
//
// draw();
}
function draw() {
// nodeGroup.selectAll('g').remove();
var gd = nodeGroup.selectAll('g').
data(AudioNode_.audioNodes,function(d){return d.id;});
var g = gd.enter()
.append('g')
.attr('transform', function (d) { return 'translate(' + (d.x - nodeWidth / 2) + ',' + (d.y - nodeHeight / 2) + ')' })
.call(drag);
g.append('rect');
g.append('text');
gd.select('rect')
.attr({ 'width': nodeWidth, 'height': nodeHeight, 'class': 'audioNode' });
gd.each(function (d) {
var sel = d3.select(this);
console.log(d.name + '**');
d.audioParams.forEach((d)=>{
console.log(d.name);
});
var gp = sel.append('g');
var gpd = gp.selectAll('g')
.data(d.audioParams,function(d){return d.id;});
var gpdg = gpd.enter()
.append('g');
gpdg.append('circle')
.attr({ 'r': pointSize / 2, cx: 0, cy: (d, i)=> { d.x = 0; d.y = (i + 1) * 20; return d.y; }, 'class': 'param' });
gpdg.append('text')
.attr({x: (d)=> (d.x + pointSize/2 + 5),y:(d)=>d.y,'class':'label' })
.text((d)=>d.name);
gpd.exit().remove();
});
gd.select('text')
.attr({ x: 0, y: -10, 'class': 'label' })
.text(function (d) { return d.name; });
g.filter(function (d) {
return d.numberOfOutputs > 0;
})
.append('rect')
.attr({ x: nodeWidth - pointSize / 2, y: nodeHeight / 2 - pointSize / 2, width: pointSize, height: pointSize, 'class': 'output' });
g.filter(function (d) { return d.numberOfInputs > 0; })
.append('rect')
.attr({ x: -pointSize / 2, y: nodeHeight / 2 - pointSize / 2, width: pointSize, height: pointSize, 'class': 'input' });
gd.exit().remove();
// line 描画
var ld = lineGroup.selectAll('path')
.data(AudioNode_.audioConnections);
ld.enter()
.append('path');
ld.each(function (d) {
var path = d3.select(this);
var x1,y1,x2,y2;
// x1,y1
if (d.from instanceof AudioNode_) {
x1 = d.from.x + nodeWidth / 2;
y1 = d.from.y;
}
if (d.to instanceof AudioNode_) {
x2 = d.to.x - nodeWidth / 2;
y2 = d.to.y;
} else {
if(d.to.param instanceof AudioParam_){
x2 = d.to.node.x + d.to.param.x - nodeWidth /2;
y2 = d.to.node.y + d.to.param.y - nodeHeight / 2;
}
}
var line = d3.svg.line()
.x(function(d){return d.x})
.y(function(d){return d.y})
.interpolate('basis');
var pos =
[
{x:x1,y:y1},
{x:x1 + (x2 - x1)/4,y:y1},
{x:x1 + (x2 - x1)/2,y:y1 + (y2 - y1)/2},
{x:x1 + (x2 - x1)*3/4,y:y2},
{x:x2, y:y2}
];
path.attr({'d':line(pos),'class':'line'});
//
// line.attr({
// x1: x1,
// y1: y1,
// x2: x2,
// y2: y2,
// 'class': 'line'
// });
});
ld.exit().remove();
}
"use strict";
var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })();
var _get = function get(_x, _x2, _x3) { var _again = true; _function: while (_again) { var object = _x, property = _x2, receiver = _x3; desc = parent = getter = undefined; _again = false; if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { _x = parent; _x2 = property; _x3 = receiver; _again = true; continue _function; } } else if ('value' in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } } };
function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
var nodeHeight = 200;
var nodeWidth = 100;
var pointSize = 16;
var svg;
var counter = 0;
var audioNodes = [];
var audioConnections = [];
var NodeViewData = function NodeViewData(x, y) {
_classCallCheck(this, NodeViewData);
this.x = x | 0;
this.y = y | 0;
};
var AudioParam_ = (function (_NodeViewData) {
_inherits(AudioParam_, _NodeViewData);
function AudioParam_(name, param) {
_classCallCheck(this, AudioParam_);
_get(Object.getPrototypeOf(AudioParam_.prototype), 'constructor', this).call(this);
this.id = counter++;
this.name = name;
this.audioParam = param;
}
return AudioParam_;
})(NodeViewData);
var AudioNode_ = (function (_NodeViewData2) {
_inherits(AudioNode_, _NodeViewData2);
function AudioNode_(audioNode) {
var _this = this;
_classCallCheck(this, AudioNode_);
_get(Object.getPrototypeOf(AudioNode_.prototype), 'constructor', this).call(this);
this.id = counter++;
this.audioNode = audioNode;
this.name = audioNode.constructor.toString().match(/function\s(.*)\(/)[1];
this.audioParams = [];
for (var i in audioNode) {
if (typeof audioNode[i] === 'function') {
this[i] = audioNode[i].bind(audioNode);
} else {
if (typeof audioNode[i] === 'object') {
this[i] = audioNode[i];
if (audioNode[i] instanceof AudioParam) {
this.audioParams.push(new AudioParam_(i, audioNode[i]));
}
} else {
Object.defineProperty(this, i, {
get: (function (i) {
return _this.audioNode[i];
}).bind(null, i),
set: (function (i, v) {
_this.audioNode[i] = v;
}).bind(null, i),
enumerable: true,
configurable: false
});
}
}
}
}
// 1つだけだとノードの削除で2つの場合はコネクションの削除
_createClass(AudioNode_, null, [{
key: 'remove',
value: function remove(node, to) {
if (!to) {
for (var i = 0; i < AudioNode_.audioNodes.length; ++i) {
if (AudioNode_.audioNodes[i] === node) {
AudioNode_.audioNodes.splice(i--, 1);
}
}
for (var i = 0; i < AudioNode_.audioConnections.length; ++i) {
var n = AudioNode_.audioConnections[i];
if (n.from === node || n.to === node) {
AudioNode_.audioConnections.splice(i--, 1);
} else {
if (!n.to.param) {
for (var d in node.audioParams) {
if (d === n.to.param) {
AudioNode_.audioConnections.splice(i--, 1);
}
};
}
}
}
} else {
for (var i = 0; i < AudioNode_.audioConnections.length; ++i) {
var _n = AudioNode_.audioConnections[i];
if (!to.param) {
if (_n.from === node && _n.to === to) {
AudioNode_.audioConnections.splice(i--, 1);
}
} else {
if (_n.from === node && _n.to.param === to.param && _n.to.node === to.node) {
AudioNode_.audioConnections.splice(i--, 1);
}
}
}
}
}
}, {
key: 'create',
value: function create(audionode) {
var obj = new AudioNode_(audionode);
AudioNode_.audioNodes.push(obj);
return obj;
}
}, {
key: 'connect',
value: function connect(from_, to_) {
// 存在チェック
for (var i = 0, l = AudioNode_.audioConnections.length; i < l; ++i) {
if (AudioNode_.audioConnections[i].from === from_ && AudioNode_.audioConnections[i].to === to_) {
throw new Error('接続が重複しています。');
}
}
if (from_ instanceof AudioNode_) {
if (to_ instanceof AudioNode_) {
from_.connect(to_.audioNode);
} else {
if (to_.param instanceof AudioParam_) {
from_.connect(to_.param.audioParam);
} else {
from_.connect(to_.node, null, to_.param);
}
}
} else if (from_.param) {
if (to_ instanceof AudioNode_) {
from_.node.connect(to_.audioNode, from_.param);
} else {
if (to_.param instanceof AudioParam_) {
from_.node.connect(to_.param.audioParam, from_.param);
} else {
from_.node.connect(to_.node, from_.param, to_.param);
}
}
} else {
throw new Error('Connection Error');
}
AudioNode_.audioConnections.push({
'from': from_,
'to': to_
});
}
}]);
return AudioNode_;
})(NodeViewData);
AudioNode_.audioNodes = [];
AudioNode_.audioConnections = [];
var nodeGroup, lineGroup;
var drag;
window.onload = function () {
var ctx = new AudioContext();
d3.select(window).on('resize', function () {
if (svg) {
svg.attr({
width: window.innerWidth,
height: window.innerHeight
});
}
});
// for(var i in ctx){
// if(i.match(/create/)){
// try {
// var o = ctx[i]();
// for(var j in o){
// if(o[j] instanceof AudioParam){
// for(var k in o[j]){
// console.log(' ',k,o[j][k] instanceof AudioParam,o[j][k]);
// }
// }
// }
//
// } catch (e){
// console.log(e);
// }
//
// }
// }
//
var osc = AudioNode_.create(ctx.createOscillator());
osc.x = 100;
osc.y = 200;
var gain = AudioNode_.create(ctx.createGain());
gain.x = 400;
gain.y = 200;
var filter = AudioNode_.create(ctx.createBiquadFilter());
filter.x = 250;
filter.y = 330;
var out = AudioNode_.create(ctx.destination);
out.x = 550;
out.y = 300;
// for(var i in osc){
// console.log(i + ':' + osc[i]);
// for(var j in osc[i]){
// console.log(' ' + j + ':' + osc[i][j]);
// }
// }
AudioNode_.connect(osc, filter);
AudioNode_.connect(osc, { node: gain, param: gain.audioParams[0] });
AudioNode_.connect(filter, gain);
AudioNode_.connect(gain, out);
console.log(AudioNode_.audioNodes.length);
console.log(AudioNode_.audioConnections.length);
console.log(AudioNode_.audioConnections[0]);
// osc.audioNode.type = 'sawtooth';
osc.type = 'sawtooth';
console.log(osc.type);
osc.frequency.value = 440;
// osc.start();
// osc.stop(ctx.currentTime + 0.1);
drag = d3.behavior.drag().origin(function (d) {
return d;
}).on("drag", function (d) {
if (d3.event.sourceEvent.shiftKey) {
console.log('aa');
return;
}
d.x += d3.event.dx;
d.y += d3.event.dy;
d3.select(this).attr('transform', 'translate(' + (d.x - nodeWidth / 2) + ',' + (d.y - nodeHeight / 2) + ')');
draw();
});
svg = d3.select('body').append('svg').attr({ 'width': window.innerWidth, 'height': window.innerHeight });
nodeGroup = svg.append('g');
lineGroup = svg.append('g');
draw();
// window.setTimeout(() => {
// //AudioNode_.remove(osc);
// //AudioNode_.remove(out);
// console.log(AudioNode_.audioNodes.length);
// console.log(AudioNode_.audioConnections.length);
// draw();
// }, 2000);
//
// draw();
};
function draw() {
// nodeGroup.selectAll('g').remove();
var gd = nodeGroup.selectAll('g').data(AudioNode_.audioNodes, function (d) {
return d.id;
});
var g = gd.enter().append('g').attr('transform', function (d) {
return 'translate(' + (d.x - nodeWidth / 2) + ',' + (d.y - nodeHeight / 2) + ')';
}).call(drag);
g.append('rect');
g.append('text');
gd.select('rect').attr({ 'width': nodeWidth, 'height': nodeHeight, 'class': 'audioNode' });
gd.each(function (d) {
var sel = d3.select(this);
console.log(d.name + '**');
d.audioParams.forEach(function (d) {
console.log(d.name);
});
var gp = sel.append('g');
var gpd = gp.selectAll('g').data(d.audioParams, function (d) {
return d.id;
});
var gpdg = gpd.enter().append('g');
gpdg.append('circle').attr({ 'r': pointSize / 2, cx: 0, cy: function cy(d, i) {
d.x = 0;d.y = (i + 1) * 20;return d.y;
}, 'class': 'param' });
gpdg.append('text').attr({ x: function x(d) {
return d.x + pointSize / 2 + 5;
}, y: function y(d) {
return d.y;
}, 'class': 'label' }).text(function (d) {
return d.name;
});
gpd.exit().remove();
});
gd.select('text').attr({ x: 0, y: -10, 'class': 'label' }).text(function (d) {
return d.name;
});
g.filter(function (d) {
return d.numberOfOutputs > 0;
}).append('rect').attr({ x: nodeWidth - pointSize / 2, y: nodeHeight / 2 - pointSize / 2, width: pointSize, height: pointSize, 'class': 'output' });
g.filter(function (d) {
return d.numberOfInputs > 0;
}).append('rect').attr({ x: -pointSize / 2, y: nodeHeight / 2 - pointSize / 2, width: pointSize, height: pointSize, 'class': 'input' });
gd.exit().remove();
// line 描画
var ld = lineGroup.selectAll('path').data(AudioNode_.audioConnections);
ld.enter().append('path');
ld.each(function (d) {
var path = d3.select(this);
var x1, y1, x2, y2;
// x1,y1
if (d.from instanceof AudioNode_) {
x1 = d.from.x + nodeWidth / 2;
y1 = d.from.y;
}
if (d.to instanceof AudioNode_) {
x2 = d.to.x - nodeWidth / 2;
y2 = d.to.y;
} else {
if (d.to.param instanceof AudioParam_) {
x2 = d.to.node.x + d.to.param.x - nodeWidth / 2;
y2 = d.to.node.y + d.to.param.y - nodeHeight / 2;
}
}
var line = d3.svg.line().x(function (d) {
return d.x;
}).y(function (d) {
return d.y;
}).interpolate('basis');
var pos = [{ x: x1, y: y1 }, { x: x1 + (x2 - x1) / 4, y: y1 }, { x: x1 + (x2 - x1) / 2, y: y1 + (y2 - y1) / 2 }, { x: x1 + (x2 - x1) * 3 / 4, y: y2 }, { x: x2, y: y2 }];
path.attr({ 'd': line(pos), 'class': 'line' });
//
// line.attr({
// x1: x1,
// y1: y1,
// x2: x2,
// y2: y2,
// 'class': 'line'
// });
});
ld.exit().remove();
}
*{
font-family: "Hiragino Kaku Gothic ProN","メイリオ", sans-serif;
}
body{
background:white;
color: black;
}
.audioNode {
fill:#BBB;
stroke-width:1px;
stroke:black;
color:black;
}
.param {
fill:red;
}
.label {
stroke:black;
font-size:8px;
}
.input {
fill:red;
}
.output{
fill:blue;
}
.line {
stroke: black;
stroke-width: 4px;
stroke-linecap: round;
fill:none;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment