Skip to content

Instantly share code, notes, and snippets.

@geisasantos
Created July 15, 2017 00:39
Show Gist options
  • Save geisasantos/7b74c39f4e7b481bb0f88db12de39ccc to your computer and use it in GitHub Desktop.
Save geisasantos/7b74c39f4e7b481bb0f88db12de39ccc to your computer and use it in GitHub Desktop.
Wikipedia Audiovisualizer (p5.js & timbre.js)
<!---------------------------
WIKIPEDIA AUDIOVISUALIZER
-----------------------------
USES P5.JS, TIMBRE.JS,
& RECONNECTING-WEBSOCKET
-----------------------------
What you are seeing/hearing are
reflections of live updates to
Wikipedia.
The size of a shape represents
the size of an update.
Blue shapes are content increases.
This is accompanied by a soft sine wave.
Red shapes are content decreases.
This is accompanied by a soft saw wave.
Triangles represent changes by
registered users.
Circles represent changes by
anonymous users.
Squares represent changes by
bots.
The screen washes out for new
users. This is accompanied by a
noise/chord drone.
Chord/note choices were slapped
together too quickly. I'd like
to have another go eventually,
when I really have a chance to
think about the composition.
----------------------------->
// Be warned... the audio glitches...
// and everything could be more performant.
// I did what I could for a saturday morning :-/
// Global Chord Cycle
var chordNo = 1;
// Cycle Global Chord
function chordChange() {
if (chordNo === 2){
chordNo = 0
} else {
chordNo++
}
setTimeout(chordChange, 8000);
}
chordChange();
// Tonal References
var pitches = [146, 165, 183, 195, 220, 244, 275, 293, 330, 367, 391, 440, 489, 550, 587, 660, 734, 783, 880, 978, 1101, 1174, 1321, 1468, 1566];
var fifths = [
[4,9],
[2,6],
[5,10]
]
var chords = [
[5,7,11,13,14,16,18,20,22,23],
[6,8,9,11,13,15,16,18,20,22],
[5,7,8,10,12,14,15,17,19,21]
];
// New user pad/drone settings
var chordEnv = [0, [0.3, 1000], [0.3, 3000], [0, 5000]];
var chordFiltEnv = [700, [1200, 1500], [1500, 2000], [1000, 2500], [1000, 5000]];
var chordCutoff = T("env", {table:chordFiltEnv}).bang();
// Utility to temper sizes
function dimval(n, min_in, max_in, min_out, max_out, exponent) {
n -= min_in
n /= max_in - min_in
n = Math.pow(n, exponent)
n *= max_out - min_out
n += min_out
return n
}
/*-------------------------------------
OLD - CODEPEN SWITCHED TO HTTPS AND
ADD THIS WEBSOCKET DOESN'T CURRENTLY
SUPPORT HTTPS:
//------------------------------------*/
// Connect to wikipedia websocket
// var ws = new ReconnectingWebSocket('ws://wikimon.hatnote.com/en/');
// ws.onmessage = function(event) {
// var dat = JSON.parse(event.data);
// processData(dat);
// };
/*-------------------------------------
NEW - I CREATED AN EVENT STREAM MUCH
LIKE THE WEBSOCKET AND HOSTED IT ON NOW
//------------------------------------*/
var es = new EventSource("https://wikimon.now.sh");
es.onmessage = function(e) {
var dat = JSON.parse(e.data);
processData(dat);
};
var shapes = [];
function processData(message) {
var shape = {};
// New user
if (message.action === "create" && message.summary === "New user account") {
newUser();
}
// Generate shape data
if (message.change_size > 0) {
shape.addition = true;
} else {
shape.addition = false;
}
var _size = Math.abs(message.change_size);
shape.size = dimval(_size, 0, 100000, 20, 4000, 0.5);
shape.title = message.page_title;
shape.anon = message.is_anon;
shape.bot = message.is_bot;
shape.x = random(windowWidth);
shape.y = random(windowHeight);
shape.speedRot = random(-1.3, 1.3);
shape.speedX = random(-2, 2);
shape.speedY = random(-2, 2);
shape.life = 120;
if (!document[hidden]) {
shapes.unshift(shape);
}
// Audio
if (!shape.addition) {
var pitch = T("saw", {freq:pitches[chords[chordNo][parseInt(random(0, 9))]], mul:0.09});
var synth = T("lpf" , {cutoff:670, Q:12}, pitch);
} else {
var synth = T("sin", {freq:pitches[chords[chordNo][parseInt(random(0, 9))]], mul:0.09});
}
synth = T("reverb", {room:2, damp:0.1, mix:0.7}, synth);
T("perc", {r:2500}, synth).on("ended", function() {
this.pause();
}).bang().play();
}
var userTimeout;
var isNewUser = false;
var newUserMod = 10;
function newUser() {
isNewUser = true;
clearTimeout(userTimeout);
userTimeout = setTimeout(function(){
isNewUser = false;
},3000);
// Audio
var _1 = T("lpf" , {cutoff:chordCutoff, Q:3}, T("sin", {freq:pitches[fifths[chordNo][0]], mul:0.05}), T("sin", {freq:pitches[fifths[chordNo][1]], mul:0.05}),T("pink", {mul:0.1}));
var _1 = T("reverb", {room:2, damp:0.1, mix:0.7}, _1)
T("env", {table:chordEnv},_1).on("ended", function() {
this.pause();
}).bang().play();
}
// Create rotation around a center point
function rotatePoint(pX, pY, cX, cY, rot) {
x = cX + (pX-cX)*Math.cos(rot) - (pY-cY)*Math.sin(rot);
y = cY + (pX-cX)*Math.sin(rot) + (pY-cY)*Math.cos(rot);
return {
x: x,
y: y
};
}
// Get triangle points from center point, size, rotation
function getTri(x,y,size,rot) {
var angles = [0,(2/3*Math.PI),(4/3*Math.PI)]
var points = {};
var r = size/2;
var _x,_y,_calc;
for (var i = 0; i < angles.length; i++) {
_x = r*cos(angles[i]) + x;
_y = r*sin(angles[i]) + y;
_calc = rotatePoint(_x, _y, x, y, rot);
points['x'+(i+1)] = _calc.x;
points['y'+(i+1)] = _calc.y;
}
return points;
}
// Get square points from center point, size, rotation
// Could be writted better, but its the weekend,
// and my brain is lazy.
function getSquare(x, y, size, rot) {
var _size = size/2;
var points = [];
var x1 = x - _size;
var y1 = y - _size;
var x2 = x + _size;
var y2 = y - _size;
var x3 = x + _size;
var y3 = y + _size;
var x4 = x - _size;
var y4 = y + _size;
points.push(rotatePoint(x1, y1, x, y, rot));
points.push(rotatePoint(x2, y2, x, y, rot));
points.push(rotatePoint(x3, y3, x, y, rot));
points.push(rotatePoint(x4, y4, x, y, rot));
return points;
}
// Setup p5
function setup() {
createCanvas(windowWidth, windowHeight);
colorMode(RGB, 120);
textFont("Monospace");
textSize(12);
}
function windowResized() {
resizeCanvas(windowWidth, windowHeight);
colorMode(RGB, 120);
}
function draw() {
// Clear the slate
background(16+(newUserMod/7),20+(newUserMod/5),26+(newUserMod/4),120-newUserMod);
for (var i = 0; i < shapes.length; i++) {
// No fill
fill(120,0);
// If addition: blue, is removal: red
if (shapes[i].addition) {
stroke(20, 120, 120, shapes[i].life);
} else {
stroke(120, 50, 50, shapes[i].life);
}
// Square if bot
if (shapes[i].bot) {
var _sq = getSquare(
shapes[i].x,
shapes[i].y,
shapes[i].size,
(radians(frameCount)*shapes[i].speedRot)
);
quad(_sq[0].x,_sq[0].y,
_sq[1].x,_sq[1].y,
_sq[2].x,_sq[2].y,
_sq[3].x,_sq[3].y);
} else
// Circle if anon
if (shapes[i].anon) {
ellipse(shapes[i].x,
shapes[i].y,
shapes[i].size,
shapes[i].size);
} else
// Triangle if friendly neighborhood user
{
var _tri = getTri(
shapes[i].x,
shapes[i].y,
shapes[i].size,
(radians(frameCount)*shapes[i].speedRot)
);
triangle(_tri.x1, _tri.y1,
_tri.x2, _tri.y2,
_tri.x3, _tri.y3);
}
stroke(0,0,0,0);
if (shapes[i].addition) {
fill(20, 120, 120, shapes[i].life);
} else {
fill(120, 50, 50, shapes[i].life);
}
textFont("Monospace");
textSize(12);
text(shapes[i].title, shapes[i].x, shapes[i].y+4);
// Drift
shapes[i].x = shapes[i].x + shapes[i].speedX;
shapes[i].y = shapes[i].y + shapes[i].speedY;
// Remove shape if life is over
// Age if still alive
if (shapes[i].life < 1) {
shapes.splice(i, 1);
} else {
shapes[i].life = shapes[i].life - 1;
}
}
if (isNewUser) {
if (newUserMod < 100) {
newUserMod = newUserMod+2;
}
} else {
if (newUserMod > 10) {
newUserMod = newUserMod-0.5;
}
}
fill(110,120);
stroke(0,0);
textSize(27);
text("WIKIPEDIA AUDIOVISUALIZER", windowWidth-windowWidth/2-205, 30);
}
document.body.style.overflow = "hidden";
<script src="https://codepen.io/halvves/pen/284687a71ed4c9aa9033b0a3838da372"></script>
<script src="https://codepen.io/halvves/pen/affd342f5bbba1263cd9d205e818958c"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.4.2/p5.min.js"></script>
<script src="https://codepen.io/halvves/pen/2bc22b3057e93e3a3c2afa0fd1d74df6"></script>
//-------------------------
// QUICK KEY
//-------------------------
// TRIANGLE = Registered User
// CIRCLE = Anonymouse User
// SQUARE = Bot
//-------------------------
// BLUE = Addition
// RED = Deletion
//-------------------------
// WASHOUT = New User
//-------------------------

Wikipedia Audiovisualizer (p5.js & timbre.js)

UPDATE: Codepen recently switched to https, but the websocket that this uses isn't currently able to work over wss. I hacked together a quick Event Stream that approximates the websocket for now and threw it up on Now.

When I first saw listen.hatnote.com, I was very inspired to try something like that of my own, but never had the time to. Saturday morning I finally had the chance to explore the idea. I had really been wanting to try out p5.js, and this was the perfect opportunity. Right now I'm using timbre.js for the audio, b/c its a library I already have basic knowledge of, but would like to eventually switch to p5.sound or directly use the Web Audio API.

USES P5.JS, TIMBRE.JS, & RECONNECTING-WEBSOCKET

What you are seeing/hearing are reflections of live updates to Wikipedia.

The size of a shape represents the size of an update.

Blue shapes are content increases. This is accompanied by a soft sine wave.

Red shapes are content decreases. This is accompanied by a soft saw wave.

Triangles represent changes by registered users.

Circles represent changes by anonymous users.

Squares represent changes by bots.

The screen washes out for new users. This is accompanied by a noise/chord drone.

Chord/note choices were slapped together too quickly. I'd like to have another go eventually, when I really have a chance to think about the composition.

A Pen by Geisa Santos on CodePen.

License.

@geisasantos
Copy link
Author

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment