Commit 61ebeecc authored by Karan Goel's avatar Karan Goel
Browse files

move structure around

parent 52c3c780
#### joe made this: https://goel.io/joe
#####=== Node ===#####
# Logs
logs
*.log
# Runtime data
pids
*.pid
*.seed
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# node-waf configuration
.lock-wscript
# Compiled binary addons (http://nodejs.org/api/addons.html)
build/Release
# Dependency directory
# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git-
node_modules
# Debug log from npm
npm-debug.log
#####=== OSX ===#####
.DS_Store
.AppleDouble
.LSOverride
# Icon must end with two \r
Icon
# Thumbnails
._*
# Files that might appear on external disk
.Spotlight-V100
.Trashes
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
// Setup basic express server
var express = require('express');
var app = express();
var server = require('http').createServer(app);
var io = require('socket.io')(server);
var port = process.env.PORT || 3000;
server.listen(port, function () {
console.log('Server listening at port %d', port);
});
// Routing
app.use(express.static(__dirname + '/public'));
// Chatroom
// usernames which are currently connected to the chat
var usernames = {};
var numUsers = 0;
io.on('connection', function (socket) {
var addedUser = false;
// when the client emits 'new message', this listens and executes
socket.on('new message', function (data) {
// we tell the client to execute 'new message'
socket.broadcast.emit('new message', {
username: socket.username,
message: data
});
});
// when the client emits 'add user', this listens and executes
socket.on('add user', function (username) {
// we store the username in the socket session for this client
socket.username = username;
// add the client's username to the global list
usernames[username] = username;
++numUsers;
addedUser = true;
socket.emit('login', {
numUsers: numUsers
});
// echo globally (all clients) that a person has connected
socket.broadcast.emit('user joined', {
username: socket.username,
numUsers: numUsers
});
});
// when the client emits 'typing', we broadcast it to others
socket.on('typing', function () {
socket.broadcast.emit('typing', {
username: socket.username
});
});
// when the client emits 'stop typing', we broadcast it to others
socket.on('stop typing', function () {
socket.broadcast.emit('stop typing', {
username: socket.username
});
});
// when the user disconnects.. perform this
socket.on('disconnect', function () {
// remove the username from global usernames list
if (addedUser) {
delete usernames[socket.username];
--numUsers;
// echo globally that this client has left
socket.broadcast.emit('user left', {
username: socket.username,
numUsers: numUsers
});
}
});
});
{
"name": "server",
"version": "0.0.0",
"description": "",
"main": "index.js",
"private": true,
"dependencies": {
"express": "3.4.8",
"socket.io": "*"
}
}
# Auto detect text files and perform LF normalization
* text=auto
# Custom for Visual Studio
*.cs diff=csharp
*.sln merge=union
*.csproj merge=union
*.vbproj merge=union
*.fsproj merge=union
*.dbproj merge=union
# Standard to msysgit
*.doc diff=astextplain
*.DOC diff=astextplain
*.docx diff=astextplain
*.DOCX diff=astextplain
*.dot diff=astextplain
*.DOT diff=astextplain
*.pdf diff=astextplain
*.PDF diff=astextplain
*.rtf diff=astextplain
*.RTF diff=astextplain
.idea\
#################
## Eclipse
#################
*.pydevproject
.project
.metadata
bin/
tmp/
*.tmp
*.bak
*.swp
*~.nib
local.properties
.classpath
.settings/
.loadpath
# External tool builders
.externalToolBuilders/
# Locally stored "Eclipse launch configurations"
*.launch
# CDT-specific
.cproject
# PDT-specific
.buildpath
#################
## Visual Studio
#################
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
# User-specific files
*.suo
*.user
*.sln.docstates
# Build results
[Dd]ebug/
[Rr]elease/
x64/
build/
[Bb]in/
[Oo]bj/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
*_i.c
*_p.c
*.ilk
*.meta
*.obj
*.pch
*.pdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*.log
*.vspscc
*.vssscc
.builds
*.pidb
*.log
*.scc
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opensdf
*.sdf
*.cachefile
# Visual Studio profiler
*.psess
*.vsp
*.vspx
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# NCrunch
*.ncrunch*
.*crunch*.local.xml
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.Publish.xml
*.pubxml
# NuGet Packages Directory
## TODO: If you have NuGet Package Restore enabled, uncomment the next line
#packages/
# Windows Azure Build Output
csx
*.build.csdef
# Windows Store app package directory
AppPackages/
# Others
sql/
*.Cache
ClientBin/
[Ss]tyle[Cc]op.*
~$*
*~
*.dbmdl
*.[Pp]ublish.xml
*.pfx
*.publishsettings
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file to a newer
# Visual Studio version. Backup files are not needed, because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
# SQL Server files
App_Data/*.mdf
App_Data/*.ldf
#############
## Windows detritus
#############
# Windows image file caches
Thumbs.db
ehthumbs.db
# Folder config file
Desktop.ini
# Recycle Bin used on file shares
$RECYCLE.BIN/
# Mac crap
.DS_Store
#############
## Python
#############
*.py[co]
# Packages
*.egg
*.egg-info
dist/
build/
eggs/
parts/
var/
sdist/
develop-eggs/
.installed.cfg
# Installer logs
pip-log.txt
# Unit test / coverage reports
.coverage
.tox
#Translations
*.mo
#Mr Developer
.mr.developer.cfg
The MIT License (MIT)
Copyright (c) 2013 Michael Bromley <michael@michaelbromley.co.uk>
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
\ No newline at end of file
# Soundcloud Visualizer
This is an experiment in using the web audio API together with canvas to make some interesting and cool-looking visualizations.
Since this is my first foray into the world of both canvas and web audio, I have slowly iterated over a number of ideas and trials which are all in the `/tests` folder.
The visual style was inspired by the artwork of the album ["The Resistance"] (http://en.wikipedia.org/wiki/File:Theresistance.jpg) by Muse.
Thanks to [Soundcloud] (https://soundcloud.com) for providing a great open API!
# Demo
[Here's a working demo] (http://www.michaelbromley.co.uk/experiments/soundcloud-vis/#muse/undisclosed-desires). Enjoy and share!
# Instructions
Go to [https://soundcloud.com] (https://soundcloud.com) and find some music. From the individual song page, copy the url and paste it into the input of the visualizer, then hit enter or press the play button.
You'll notice that part of the the track URL is added to the visualizer's URL. This makes it possible to easily save or share the state. So, if you find a good track that looks cool with
the visualization that you want to share, just give the new URL to someone and when they load it up, it'll automatically start streaming and visualizing that track.
## Playlists
Playlists (also known as "sets" in Soundcloud) can now be pasted into the visualizer, which will cause the whole playlist to play in sequence. You can navigate the playlist using the controls below.
## Controls
- `spacebar` = toggle play/pause
- `>` (right arrow key) = skip forward to next track in playlist
- `<` (left arrow key) = skip backward to previous track in playlist
# Issues
Since the web audio API is pretty new, browser support is patchy:
- *Chrome* Latest versions works well, that's what I've been building in.
- *Firefox* This *should* work, since it has supported web audio since version 25, but in my tests the audio fails to play. Any suggestions?
- *Safari* I haven't tested it since I'm stuck with the dead Windows version, but I guess it should work since it's WebKit.
- *IE* Nope.
- *Opera* Not tested but maybe now that they switched to WebKit.
# Improvements
I can think of a few improvements that I might implement, or someone else might want to:
~~- Support for playlists. So you can paste the playlist URL and it just keeps going through to the end of the playlist.~~ - DONE (thanks to [adg29](https://github.com/adg29))
- More types of visualization. I've written the code in a pretty modular way so it's simple to write a visualization that can plug into the app (see below)
- Better browser support. I'm sure there can be more done on this to get it working at least in Firefox and mobile WebKit browsers, with perhaps fallbacks for IE.
# Writing visualizations
If you want to fork this and write your own visualizations, they way I've set it up is pretty simple:
The audio data from Soundcloud is processed by the SoundCloudAudioSource object, which exposes two properties, `volume` and `streamData`, which are continuously updated in real-time.
`streamData` is an array of 128 integers from 0-255, representing the volume of the signal at each frequency.
`volume` is a single integer representing the overall volume, useful for things like pulsing effects that reflect the beat of the audio track.
Your visualization object just needs to consume this object and then it will be able to do whatever it wants to with the data provided. The visualization object should make
it's own canvas elements and append them to the `#visualizer` div.
# License
MIT
<!DOCTYPE html>
<html>
<head>
<title></title>
<script>
var danceFloor = (function() {
var module = {};
var tileSize;
var numBlocks;
var tiles = [];
var canvas;
var ctx;
var cycler = 0;
var gr = 0.618033988749895 // golden ratio
function Tile(x, y, freq, tileSize, ctx) {
this.tileSize = tileSize;
this.ctx = ctx;
this.x = x;
this.y = y;
this.freq = freq;
}
Tile.prototype.drawTile = function() {
var freq = this.freq;
var r = Math.round(Math.sin(cycler*freq*gr)*127) + 128;
var g = Math.round(Math.sin(cycler*freq*gr+2)*127) + 128;
var b = Math.round(Math.sin(cycler*freq*gr+4)*127) + 128;
this.ctx.fillStyle = "rgb(" + r + ", " + g + ", " + b + ")";
this.ctx.fillRect (this.x, this.y, this.tileSize, this.tileSize);
};
module.init = function(canvasId, size) {
canvas = document.getElementById(canvasId);
ctx = canvas.getContext("2d");
var canvasWidth = canvas.width;
var canvasHeight = canvas.height;
tileSize = size;
numBlocks = (canvasWidth*canvasHeight) / (tileSize*tileSize);
for(var x = 0; x < numBlocks; x += 1) {
var xCoord = (x * tileSize) % canvasWidth;
var yCoord = Math.floor(x/(canvasWidth / tileSize)) * tileSize;
var freq = Math.random();
tiles.push(new Tile(xCoord, yCoord, freq, tileSize, ctx));
}
module.draw();
};
module.draw = function() {
tiles.forEach(function(tile) {
tile.drawTile();
});
cycler += 0.05;
requestAnimationFrame(module.draw);
};
return module;
})();
</script>
<style type="text/css">
canvas {
border: 1px solid black;
}
</style>
</head>
<body onload="danceFloor.init('canvas', 40);">
<canvas id="canvas" width="400" height="400"></canvas>
</body>
</html>
\ No newline at end of file
<!DOCTYPE html>
<html>
<head>
<title></title>
<script>
var danceFloor = (function() {
var module = {};
var tileSize;
var tiles = [];
var canvas;
var ctx;
var cycler = 0;
var gr = 0.618033988749895; // golden ratio
function Polygon(sides, x, y, freq, tileSize, ctx) {
this.sides = sides;
this.tileSize = tileSize;
this.ctx = ctx;
this.x = x;
this.y = y;
this.freq = freq;
}
Polygon.prototype.drawPolygon = function() {
this.ctx.beginPath();
this.ctx.moveTo (this.x + this.tileSize * Math.cos(0 + Math.PI/6), this.y + this.tileSize * Math.sin(0 + Math.PI/6));
for (var i = 1; i <= this.sides;i += 1) {
var x = this.x + this.tileSize * Math.cos(i * 2 * Math.PI / this.sides + Math.PI/6);
var y = this.y + this.tileSize * Math.sin(i * 2 * Math.PI / this.sides + Math.PI/6);
this.ctx.lineTo (x, y);
//console.log("x: " + x + ", y: " + y);
}
var freq = this.freq;
var r = Math.round(Math.sin(cycler*freq*gr+1)*127) + 128;
var g = Math.round(Math.sin(cycler*freq*gr+2)*127) + 128;
var b = Math.round(Math.sin(cycler*freq*gr+4)*127) + 128;
this.ctx.fillStyle = "rgb(" + r + ", " + g + ", " + b + ")";
this.ctx.strokeStyle = "black";
this.ctx.lineWidth = 1;
this.ctx.fill();
this.ctx.stroke();
};
module.makePolygonArray = function()
{
tiles = [];
var canvasWidth = canvas.width;
var canvasHeight = canvas.height;
var xStep = Math.round(Math.cos(Math.PI/6)*tileSize*2);
var yStep = Math.round(Math.cos(Math.PI/3)*tileSize + tileSize);
var xCoord = -xStep;
var yCoord = 0;
while(yCoord < canvasHeight + yStep) {
if (xCoord > canvasWidth) {
xCoord = (xCoord % xStep === 0) ? xStep/2 : 0;
yCoord += yStep;
}
else {
xCoord += xStep;
}
var freq = Math.random();
tiles.push(new Polygon(6, xCoord, yCoord, freq, tileSize, ctx));
}
};
module.resize = function() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
module.makePolygonArray();
};