Skip to content
Snippets Groups Projects
Commit 940e7b13 authored by Micah Elizabeth Scott's avatar Micah Elizabeth Scott
Browse files

Add a triangle FFT visualizer tweaked for mic input

parent 7d891bc8
No related branches found
No related tags found
No related merge requests found
/*
* Simple Open Pixel Control client for Processing,
* designed to sample each LED's color from some point on the canvas.
*
* Micah Elizabeth Scott, 2013
* This file is released into the public domain.
*/
import java.net.*;
import java.util.Arrays;
public class OPC
{
Socket socket;
OutputStream output;
String host;
int port;
int[] pixelLocations;
byte[] packetData;
byte firmwareConfig;
String colorCorrection;
boolean enableShowLocations;
OPC(PApplet parent, String host, int port)
{
this.host = host;
this.port = port;
this.enableShowLocations = true;
parent.registerDraw(this);
}
// Set the location of a single LED
void led(int index, int x, int y)
{
// For convenience, automatically grow the pixelLocations array. We do want this to be an array,
// instead of a HashMap, to keep draw() as fast as it can be.
if (pixelLocations == null) {
pixelLocations = new int[index + 1];
} else if (index >= pixelLocations.length) {
pixelLocations = Arrays.copyOf(pixelLocations, index + 1);
}
pixelLocations[index] = x + width * y;
}
// Set the location of several LEDs arranged in a strip.
// Angle is in radians, measured clockwise from +X.
// (x,y) is the center of the strip.
void ledStrip(int index, int count, float x, float y, float spacing, float angle, boolean reversed)
{
float s = sin(angle);
float c = cos(angle);
for (int i = 0; i < count; i++) {
led(reversed ? (index + count - 1 - i) : (index + i),
(int)(x + (i - (count-1)/2.0) * spacing * c + 0.5),
(int)(y + (i - (count-1)/2.0) * spacing * s + 0.5));
}
}
// Set the location of several LEDs arranged in a grid. The first strip is
// at 'angle', measured in radians clockwise from +X.
// (x,y) is the center of the grid.
void ledGrid(int index, int stripLength, int numStrips, float x, float y,
float ledSpacing, float stripSpacing, float angle, boolean zigzag)
{
float s = sin(angle + HALF_PI);
float c = cos(angle + HALF_PI);
for (int i = 0; i < numStrips; i++) {
ledStrip(index + stripLength * i, stripLength,
x + (i - (numStrips-1)/2.0) * stripSpacing * c,
y + (i - (numStrips-1)/2.0) * stripSpacing * s, ledSpacing,
angle, zigzag && (i % 2) == 1);
}
}
// Set the location of 64 LEDs arranged in a uniform 8x8 grid.
// (x,y) is the center of the grid.
void ledGrid8x8(int index, float x, float y, float spacing, float angle, boolean zigzag)
{
ledGrid(index, 8, 8, x, y, spacing, spacing, angle, zigzag);
}
// Should the pixel sampling locations be visible? This helps with debugging.
// Showing locations is enabled by default. You might need to disable it if our drawing
// is interfering with your processing sketch, or if you'd simply like the screen to be
// less cluttered.
void showLocations(boolean enabled)
{
enableShowLocations = enabled;
}
// Enable or disable dithering. Dithering avoids the "stair-stepping" artifact and increases color
// resolution by quickly jittering between adjacent 8-bit brightness levels about 400 times a second.
// Dithering is on by default.
void setDithering(boolean enabled)
{
if (enabled)
firmwareConfig &= ~0x01;
else
firmwareConfig |= 0x01;
sendFirmwareConfigPacket();
}
// Enable or disable frame interpolation. Interpolation automatically blends between consecutive frames
// in hardware, and it does so with 16-bit per channel resolution. Combined with dithering, this helps make
// fades very smooth. Interpolation is on by default.
void setInterpolation(boolean enabled)
{
if (enabled)
firmwareConfig &= ~0x02;
else
firmwareConfig |= 0x02;
sendFirmwareConfigPacket();
}
// Put the Fadecandy onboard LED under automatic control. It blinks any time the firmware processes a packet.
// This is the default configuration for the LED.
void statusLedAuto()
{
firmwareConfig &= 0x0C;
sendFirmwareConfigPacket();
}
// Manually turn the Fadecandy onboard LED on or off. This disables automatic LED control.
void setStatusLed(boolean on)
{
firmwareConfig |= 0x04; // Manual LED control
if (on)
firmwareConfig |= 0x08;
else
firmwareConfig &= ~0x08;
sendFirmwareConfigPacket();
}
// Set the color correction parameters
void setColorCorrection(float gamma, float red, float green, float blue)
{
colorCorrection = "{ \"gamma\": " + gamma + ", \"whitepoint\": [" + red + "," + green + "," + blue + "]}";
sendColorCorrectionPacket();
}
// Set custom color correction parameters from a string
void setColorCorrection(String s)
{
colorCorrection = s;
sendColorCorrectionPacket();
}
// Send a packet with the current firmware configuration settings
void sendFirmwareConfigPacket()
{
if (output == null) {
// We'll do this when we reconnect
return;
}
byte[] packet = new byte[9];
packet[0] = 0; // Channel (reserved)
packet[1] = (byte)0xFF; // Command (System Exclusive)
packet[2] = 0; // Length high byte
packet[3] = 5; // Length low byte
packet[4] = 0x00; // System ID high byte
packet[5] = 0x01; // System ID low byte
packet[6] = 0x00; // Command ID high byte
packet[7] = 0x02; // Command ID low byte
packet[8] = firmwareConfig;
try {
output.write(packet);
} catch (Exception e) {
dispose();
}
}
// Send a packet with the current color correction settings
void sendColorCorrectionPacket()
{
if (colorCorrection == null) {
// No color correction defined
return;
}
if (output == null) {
// We'll do this when we reconnect
return;
}
byte[] content = colorCorrection.getBytes();
int packetLen = content.length + 4;
byte[] header = new byte[8];
header[0] = 0; // Channel (reserved)
header[1] = (byte)0xFF; // Command (System Exclusive)
header[2] = (byte)(packetLen >> 8);
header[3] = (byte)(packetLen & 0xFF);
header[4] = 0x00; // System ID high byte
header[5] = 0x01; // System ID low byte
header[6] = 0x00; // Command ID high byte
header[7] = 0x01; // Command ID low byte
try {
output.write(header);
output.write(content);
} catch (Exception e) {
dispose();
}
}
// Automatically called at the end of each draw().
// This handles the automatic Pixel to LED mapping.
// If you aren't using that mapping, this function has no effect.
// In that case, you can call setPixelCount(), setPixel(), and writePixels()
// separately.
void draw()
{
if (pixelLocations == null) {
// No pixels defined yet
return;
}
if (output == null) {
// Try to (re)connect
connect();
}
if (output == null) {
return;
}
int numPixels = pixelLocations.length;
int ledAddress = 4;
setPixelCount(numPixels);
loadPixels();
for (int i = 0; i < numPixels; i++) {
int pixelLocation = pixelLocations[i];
int pixel = pixels[pixelLocation];
packetData[ledAddress] = (byte)(pixel >> 16);
packetData[ledAddress + 1] = (byte)(pixel >> 8);
packetData[ledAddress + 2] = (byte)pixel;
ledAddress += 3;
if (enableShowLocations) {
pixels[pixelLocation] = 0xFFFFFF ^ pixel;
}
}
writePixels();
if (enableShowLocations) {
updatePixels();
}
}
// Change the number of pixels in our output packet.
// This is normally not needed; the output packet is automatically sized
// by draw() and by setPixel().
void setPixelCount(int numPixels)
{
int numBytes = 3 * numPixels;
int packetLen = 4 + numBytes;
if (packetData == null || packetData.length != packetLen) {
// Set up our packet buffer
packetData = new byte[packetLen];
packetData[0] = 0; // Channel
packetData[1] = 0; // Command (Set pixel colors)
packetData[2] = (byte)(numBytes >> 8);
packetData[3] = (byte)(numBytes & 0xFF);
}
}
// Directly manipulate a pixel in the output buffer. This isn't needed
// for pixels that are mapped to the screen.
void setPixel(int number, color c)
{
int offset = 4 + number * 3;
if (packetData == null || packetData.length < offset + 3) {
setPixelCount(number + 1);
}
packetData[offset] = (byte) (c >> 16);
packetData[offset + 1] = (byte) (c >> 8);
packetData[offset + 2] = (byte) c;
}
// Read a pixel from the output buffer. If the pixel was mapped to the display,
// this returns the value we captured on the previous frame.
color getPixel(int number)
{
int offset = 4 + number * 3;
if (packetData == null || packetData.length < offset + 3) {
return 0;
}
return (packetData[offset] << 16) | (packetData[offset + 1] << 8) | packetData[offset + 2];
}
// Transmit our current buffer of pixel values to the OPC server. This is handled
// automatically in draw() if any pixels are mapped to the screen, but if you haven't
// mapped any pixels to the screen you'll want to call this directly.
void writePixels()
{
if (packetData == null || packetData.length == 0) {
// No pixel buffer
return;
}
if (output == null) {
// Try to (re)connect
connect();
}
if (output == null) {
return;
}
try {
output.write(packetData);
} catch (Exception e) {
dispose();
}
}
void dispose()
{
// Destroy the socket. Called internally when we've disconnected.
if (output != null) {
println("Disconnected from OPC server");
}
socket = null;
output = null;
}
void connect()
{
// Try to connect to the OPC server. This normally happens automatically in draw()
try {
socket = new Socket(host, port);
socket.setTcpNoDelay(true);
output = socket.getOutputStream();
println("Connected to OPC server");
} catch (ConnectException e) {
dispose();
} catch (IOException e) {
dispose();
}
sendColorCorrectionPacket();
sendFirmwareConfigPacket();
}
}
/*
* Object for keeping track of the layout of our triangular grid.
* The triangle is made of cells, which have information about their
* connectedness to nearby cells.
*/
public class TriangleGrid
{
Cell[] cells;
class Cell
{
PVector center;
int[] neighbors;
Cell(float cx, float cy, int n1, int n2, int n3)
{
this.center = new PVector(cx, cy);
this.neighbors = new int[3];
this.neighbors[0] = n1;
this.neighbors[1] = n2;
this.neighbors[2] = n3;
}
};
void grid16()
{
// Layout for a 16-cell triangular grid.
// Each triangle side is 1 unit. "h" is the triangle height
float h = sin(radians(60));
cells = new Cell[16];
// Bottom row, left to right
cells[ 0] = new Cell( 0.0, h*0 + h*1/3, -1, 1, -1 );
cells[ 1] = new Cell( 0.5, h*0 + h*2/3, 11, 2, 0 );
cells[ 2] = new Cell( 1.0, h*0 + h*1/3, -1, 3, 1 );
cells[ 3] = new Cell( 1.5, h*0 + h*2/3, 9, 4, 2 );
cells[ 4] = new Cell( 2.0, h*0 + h*1/3, -1, 5, 3 );
cells[ 5] = new Cell( 2.5, h*0 + h*2/3, 7, 6, 4 );
cells[ 6] = new Cell( 3.0, h*0 + h*1/3, -1, -1, 5 );
// Second row, right to left
cells[ 7] = new Cell( 2.5, h*1 + h*1/3, 5, 8, -1 );
cells[ 8] = new Cell( 2.0, h*1 + h*2/3, 12, 9, 7 );
cells[ 9] = new Cell( 1.5, h*1 + h*1/3, 3, 10, 8 );
cells[10] = new Cell( 1.0, h*1 + h*2/3, 14, 11, 9 );
cells[11] = new Cell( 0.5, h*1 + h*1/3, 1, -1, 10 );
// Third row, left to right
cells[12] = new Cell( 1.0, h*2 + h*1/3, 8, 13, -1 );
cells[13] = new Cell( 1.5, h*2 + h*2/3, 15, 14, 13 );
cells[14] = new Cell( 2.0, h*2 + h*1/3, 10, -1, 14 );
// Top
cells[15] = new Cell( 1.5, h*3 + h*1/3, 13, -1, -1 );
// Move the centroid to the origin
translate(-1.5, -h*4/3);
}
void leds(OPC opc, int index)
{
// Create LED mappings, using the current grid coordinates
for (int i = 0; i < cells.length; i++) {
opc.led(index + i, int(cells[i].center.x + 0.5), int(cells[i].center.y + 0.5));
}
}
void translate(float x, float y)
{
// Translate all points by this amount
PVector t = new PVector(x, y);
for (int i = 0; i < cells.length; i++) {
cells[i].center.add(t);
}
}
void mirror()
{
// Mirror all points left-to-right
for (int i = 0; i < cells.length; i++) {
cells[i].center.x = -cells[i].center.x;
}
}
void scale(float s)
{
// Scale all points by this amount
for (int i = 0; i < cells.length; i++) {
cells[i].center.mult(s);
}
}
void rotate(float angle)
{
// Rotate all points around the origin by this angle, in radians
for (int i = 0; i < cells.length; i++) {
cells[i].center.rotate(angle);
}
}
};
File added
examples/processing/triangle16_particle_fft_input/data/colors.png

1.93 KiB

examples/processing/triangle16_particle_fft_input/data/dot.png

2.44 KiB

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<meta http-equiv="Content-Style-Type" content="text/css">
<title>SampleSwap Licensing Information</title>
<meta name="Generator" content="Cocoa HTML Writer">
<meta name="CocoaVersion" content="1038.36">
<style type="text/css">
p.p2 {margin: 0.0px 0.0px 12.0px 0.0px; font: 12.0px Times}
p.p3 {margin: 0.0px 0.0px 12.0px 0.0px; font: 15.0px Times}
</style>
</head>
<body>
<h3 style="margin: 0.0px 0.0px 14.0px 0.0px; font: 14.0px Times"><b>Q&amp;A / LICENSE INFO FOR THE SOUNDS IN THE SAMPLESWAP COLLECTION</b></h3>
<p class="p2"><i>Last Updated: February 2012</i></p>
<p class="p2"><i>If you received a copy of these sounds from a friend (or over a file sharing network like BitTorrent) please come by and visit </i><a href="http://SampleSwap.org"><b><i>SampleSwap.org</i></b></a><i>, the community that arranged these samples for your enjoyment. Thanks!</i></p>
<p class="p2"><i>You can also find us at </i><a href="http://facebook.com/sampleswap"><i>facebook.com/sampleswap</i></a></p>
<p class="p2"><b>Q: Where do these sounds come from?</b></p>
<p class="p3"><b>http://www.sampleswap.org</b></p>
<p class="p2">These sounds were anonymously uploaded by members of the SampleSwap community between 2000 and 2012. Members are asked to only upload original sound files to which they own the copyright, and which they are willing to release into the public domain ("royalty free").</p>
<p class="p2"><b>Q: Can I re-distribute these samples?</b></p>
<p class="p2">Feel free to give away these samples as you see fit as long <i>as you include this notice along with the sample(s)</i>. Please do not sell copies of this collection, or individual samples within the collection. That's not really keeping with the spirit of things, and you may be exposing yourself to legal action.</p>
<p class="p2"><b>Q: Can I use these sounds in a public/commercial music production?</b></p>
<p class="p2"><b><i>IMPORTANT NOTE: Whether you downloaded these sounds, or received them as a gift for a donation to SampleSwap.org, you do NOT have a guarantee that these sounds are free and clear of any copyright restrictions.</i></b></p>
<p class="p2">Our membership policy is to require that users only upload original sounds that they are giving up into the public domain. But there's no way for us to be sure that 100% of the sounds in this collection are free and clear from any copyright restrictions, and you should assume that some of the sounds in this collection are not appropriate for commercial use (or even use in a non-commercial but public production.)</p>
<p class="p2">The decision to use or refrain from using these samples in a public or commercial production is entirely your own, and SampleSwap.org's owner and members do not grant you any special license or guarantee that these samples can be used without risk of violating copyright. You and you alone are responsible for the legal ramifications of using any sounds in this collection.</p>
<p class="p2"><b>Some common-sense (non-legally binding) advice is this:</b> use your own judgement when integrating samples into your songs, whether they come from SampleSwap or from a commercial sample library. If a sample has lyrics, try googling for those lyrics to find out if they're from a well known song. If a loop sounds like it's been lifted from vinyl, maybe it has been. Cut it up, modify, slice and rearrange the samples you use so that they are authentically your own. It makes your music more interesting, and it may help to protect you from unintentionally infringing on someone's copyright.</p>
<p class="p2">If you have any questions about this collection, please contact:</p>
<p class="p2">- Canton Becker / canton@gmail.com</p>
</body>
</html>
// Some real-time FFT! This visualizes music in the frequency domain using a
// polar-coordinate particle system. Particle size and radial distance are modulated
// using a filtered FFT. Color is sampled from an image.
import ddf.minim.analysis.*;
import ddf.minim.*;
OPC opc;
PImage dot;
PImage colors;
TriangleGrid triangle;
Minim minim;
AudioInput in;
FFT fft;
float[] fftFilter;
float spin = 0.003;
float radiansPerBucket = radians(2);
float decay = 0.96;
float opacity = 20;
float minSize = 0.1;
float sizeScale = 0.5;
void setup()
{
size(300, 300, P3D);
minim = new Minim(this);
// Small buffer size!
in = minim.getLineIn();
fft = new FFT(in.bufferSize(), in.sampleRate());
fftFilter = new float[fft.specSize()];
dot = loadImage("dot.png");
colors = loadImage("colors.png");
// Connect to the local instance of fcserver
opc = new OPC(this, "127.0.0.1", 7890);
// Map our triangle grid to the center of the window
triangle = new TriangleGrid();
triangle.grid16();
triangle.mirror();
triangle.rotate(radians(60));
triangle.scale(height * 0.2);
triangle.translate(width * 0.5, height * 0.5);
triangle.leds(opc, 0);
}
void draw()
{
background(0);
fft.forward(in.mix);
for (int i = 0; i < fftFilter.length; i++) {
fftFilter[i] = max(fftFilter[i] * decay, log(1 + fft.getBand(i)) * (1 + i * 0.01));
}
for (int i = 0; i < fftFilter.length; i += 3) {
color rgb = colors.get(int(map(i, 0, fftFilter.length-1, 0, colors.width-1)), colors.height/2);
tint(rgb, fftFilter[i] * opacity);
blendMode(ADD);
float size = height * (minSize + sizeScale * fftFilter[i]);
PVector center = new PVector(width * (fftFilter[i] * 0.2), 0);
center.rotate(millis() * spin + i * radiansPerBucket);
center.add(new PVector(width * 0.5, height * 0.5));
image(dot, center.x - size/2, center.y - size/2, size, size);
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment