musical pool

using d3 for a musical app

by hrabinowitz

HTML

<script src="http://mohayonao.github.io/timbre.js/timbre.js"></script>
<div id="wrap">
    <div id="left" class="inner">
    <h1>Musical pool</h1>
    <p>This was made by <a href="http://twitter.com/nsonnad">Nikhil Sonnad</a>, but the concept was inspired by the beautiful <a href="http://www.ngv.vic.gov.au/whats-on/exhibitions/exhibitions/celeste-boursier-mougenot-clinamen"><em>clinamen</em> art installation by Céleste Boursier-Mougenot</a>, in which a small symphony is produced by porcelain bowls floating in a pool of water. The piece is now on display at the National Gallery of Victoria, Melbourne.</p>

    <p>In this online 'version', white circles move about randomly and upon contact emit a pitch proportional to their size. You can drag them around yourself, as well, or change the musical scale used to generate pitch. (<a href="https://github.com/nsonnad/musical-plates">View source</a>)
    <br />
        <h4>Pitch lies along:</h4>
        <div class="btn-group btn-group-vertical">
            <button class="btn scale" id="pentatonic">Pentatonic scale</button>
            <button class="btn scale" id="blues">Blues scale</button>
            <button class="btn scale" id="aeolian">Aeolian mode</button>
            <button class="btn scale" id="chromatic">Chromatic scale</button>
        </div>
    </div>

    <div id="right" class="inner">
        <div id="chart"></div>
    </div>
    <footer><p>Visual built in <a href="http://d3js.org/">d3.js</a>, audio powered by <a href="http://mohayonao.github.io/timbre.js/">timbre.js</a>. Runs best in Google Chrome.</p></footer>
</div>

JavaScript

var margin = 30,
    w = 650 - margin * 2,
    h = w,
    radius = w / 2,
    strokeWidth = 4,
    hyp2 = Math.pow(radius, 2),
    nodeBaseRad = 5,
    nodeCount = 50;

function frequency(i) {
    // formula for getting frequency according to distance from A440
    var f = Math.pow(Math.pow(2, 1/12), i) * 220;
    return f;
}

d3.selection.prototype.moveToFront = function() {
    return this.each(function() { this.parentNode.appendChild(this);});
};

// scales, according to distance from A440
var freqs = {
    'pentatonic': [36, 33, 31, 28, 26, 24, 21, 19, 16, 14, 12, 9, 7, 4, 2, 0],
    'blues': [36, 34, 31, 30, 29, 27, 24, 22, 19, 18, 17, 14, 12, 10, 7, 6, 5, 3, 0],
    'aeolian': [36, 34, 32, 31, 29, 27, 26, 24, 22, 20, 19, 17, 15, 14, 12, 10, 8, 7, 5, 3, 2, 0],
    'chromatic': [24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1]
};

// settings for timbre.js sounds
var sin = T("sin", {freq:440, mul:0.5}),
    perc = T("perc", {a:30, r:300}, sin);

var svg = d3.select('#chart').append('svg')
    .attr('width', w + margin * 2)
    .attr('height', h + margin * 2)
.append('g')
    .attr('transform', 'translate(' + margin + ',' + margin + ')');

var pool = svg.append('circle')
    .style('stroke-width', strokeWidth * 2)
    .attr({
        class: 'pool',
        r: radius,
        cy: 0,
        cx: 0,
        transform: 'translate(' + w / 2 + ',' + h / 2 + ')'
    });

function randomNodes(n, scale) {
    var data = [],
        range = d3.range(n),
        notes = freqs[scale];

    for (var i = range.length - 1; i >= 0; i--) {
        r = Math.floor(Math.random() * (notes.length)) + 1
        console.log(r)
        data.push({
            rad: r * 1.2,
            freq: frequency(notes[r - 1])
        });
    }

    return data;
}

var force = d3.layout.force()
    .charge(0)
    //.friction(0.92)
    //.friction(0.5)
    .friction(0.95)
    .gravity(0)
    .alpha(-5)
    .size([w, h]);

var touched = svg.append("circle")
   ...