I do wish that the ripples came up more immediately when the notes are struck. Lagging behind so much made me think they were just random for a while.
/**
* Picture pallet - Dec 2009
* by volts (Tom Blackwell)
* Utility sketch that makes a pallet from random colors in a picture file
* coulda been a class...
*
*/
int pallet_size = 512, colors = 0;
color[] pallete = new color[pallet_size];
color randomColor() {
return pallete[int(random(colors))];
}
void makePallete(String sourcefile) {
PImage source;
source = loadImage(sourcefile);
//scan source image adding colors to pallet
for (int x=0; x<source.width; x++){
for (int y=0; y<source.height; y++) {
if (colors>=pallet_size)break; // pallete is full
if ((random(1) < 0.1)) continue; // randomly sample 10%
color c = source.get(x,y);
for (int n=0; n<pallet_size; n++) {
if (c==pallete[n]) { //existing color
break;
}
else if (n > colors) { //new color
pallete[colors] = c;
colors++;
break;
}
}
}
}
}
/**
* Ripples - Dec 2009
* by volts (Tom Blackwell)
* Utility sketch that simulates defraction from waves in fluid
* and shadows the ripples for better effect over flat colors.
* Algorithm based on http://freespace.virgin.net/hugo.elias/graphics/x_water.htm
*
*/
float damping = .95; // efficiency of wave propagation
float refraction = 2.; //
int darkness = 1000; // darkness of shadow
// surface elevations
float previous[] = new float[X_SIZE*Y_SIZE];
float current[] = new float[X_SIZE*Y_SIZE];
float swap[]; //swap pointer
void simulate(){
swap = current;
current = previous;
previous = swap;
for (int i=width;i<width*(height-1);i++) {
current[i] = damping*((( previous[i-1] + previous[i+1] + previous[i-width] + previous[i+width])/2.) -current[i]);
}
}
void drawRipples(PImage p){
simulate();
defract(p);
}
// renders directly to display, be sure to call loadPixels() in setup() of main sketch
void defract(PImage texture){
for(int y=1;y<height-1;y++){
for(int x=1;x<width-1;x++){
float x_offset = (current[(y*width)+(x+1)]+current[(y*width)+(x-1)])/refraction;
float y_offset = (current[(y+1)*width+x]+current[(y-1)*width+x])/refraction;
int x_previous = (int)(x + x_offset);
int y_previous = (int)(y + y_offset);
if(x_previous < 1 || x_previous> width-1) x_previous = x - (int)x_offset;
if(y_previous < 1 || y_previous > height-1) y_previous = y - (int)y_offset;
int tex_coord = (int)((x_previous)+(int)(y_previous)*texture.width);
color shadow = color(constrain( ((int)((x_offset+y_offset)*darkness)), 0, 255));
pixels[x+y*width] = blendColor(shadow,texture.pixels[tex_coord],DIFFERENCE);
}
}
updatePixels();
}
void squareWave(int x, int y, float power, int sqsize){
if(x<width-sqsize && y<height-sqsize){
for(int xx=0;xx<sqsize;xx++){
for(int yy=0;yy<sqsize;yy++){
current[((y+yy)*width)+x+xx] -= power;
}
}
}
}
/**
* Water Music - Dec 2009
* by volts (Tom Blackwell)
* Left mouse generates a new score. Right mouse changes instruments.
*
*/
import arb.soundcipher.*; // very nice library
// Depends on utility sketches picture_pallet.pde, ripples.pde
// Main Constants
int MIDDLE_C = 60; // per MIDI spec
int TEMPO = 250; // beats per minute,
int X_SIZE = 580, Y_SIZE = 400; //display width and height
int NOTES = 25, MIDDLE_C_POS = 12; // number of vertical note positions, location of middle C note
String palletsource = "sarah_chapman_louvre.jpg"; // thanks to http://www.flickr.com/photos/horrorhotel/ for CC image
SoundCipher sc = new SoundCipher();
SCScore score;
float[][] instruments = {{sc.TIMPANI, sc.TINKLE_BELL, sc.BOTTLE},
{sc.WOODBLOCKS,sc.XYLOPHONE, sc.BELLS},
{sc.TELEPHONE, sc.TENOR, sc.TAIKO},
{sc.VOICE,sc.WHISTLE,sc.XYLOPHONE},
{sc.BOWED_GLASS, sc.CRYSTAL, sc.FANTASIA, sc.FLUTE, sc.KALIMBA},
{sc.BELL,sc.BELLS},{sc.BOTTLE,sc.BOTTLE_BLOW},
{sc.BRIGHT_ACOUSTIC, sc.BRIGHTNESS},
{sc.SITAR, sc.SHANNAI, sc.SHAMISEN}};
int ensemble = 0;
PGraphics grid; // background grid
PImage P; //buffer display window
int beats; // number of beats that fit across the display window
int box_spacing, box_size; // boxes in display window
int x_error, y_error; // boxes might not divide evenly into width, height.
int x_margin, y_margin; // margin from edges of display window
int cur_beat; // the current beat, incremented by call-back
boolean played; // have we played the current score?
boolean[][] notes; // beats across, notes vertical
void setup(){
size(X_SIZE, Y_SIZE);
loadPixels();
grid = createGraphics(X_SIZE, Y_SIZE, P2D);
grid.loadPixels();
makePallete(palletsource);
P = createImage(width,height,RGB);
P.loadPixels();
box_spacing = Y_SIZE/NOTES;
box_size = box_spacing-2;
y_error = Y_SIZE % NOTES;
x_error = X_SIZE % NOTES;
y_margin = y_error/2;
x_margin = x_error/2;
beats = X_SIZE/box_spacing;
notes = new boolean[beats][NOTES];
score = new SCScore();
score.addCallbackListener(this);
makeMusic();
}
void draw(){
drawGrid();
drawNotes(cur_beat);
drawRipples(grid);
}
void stop(){
score.stop();
}
void mousePressed(){
if (mouseButton == LEFT){
makeMusic();
} else {
nextEnsemble();
makeScore();
}
}
// handle soundcipher score callbacks
public void handleCallbacks(int beat) {
if (beat == beats & played) {
nextEnsemble(); //Change ensemble each time score has been played
if (ensemble==0) {
makeMusic(); //New music after playing all ensembles
} else {
makeScore();
}
played = false;
} else {
played = true;
}
cur_beat = beat;
}
// background grid colors from pallete
void drawGrid(){
grid.background(255);
grid.beginDraw();
grid.fill(255);
grid.stroke(0,0,0);
rectMode(CORNER);
randomSeed(1); //generate same pattern on each draw()
for (int x=x_margin; x<X_SIZE-x_margin-box_spacing/2; x+=box_spacing){
for (int y=y_margin; y<Y_SIZE-y_margin; y+=box_spacing){
grid.fill(pallete[(int)random(pallet_size)]);
grid.rect(x,y,box_size, box_size);
}
}
grid.endDraw();
}
// notes are white, red in current beat
void drawNotes(int cur_beat){
for (int beat=0; beat<beats; beat++){
for (int note=0; note<NOTES; note++){
if (notes[beat][note]) {
int x = box_spacing*beat+x_margin; int y = box_spacing*note+y_margin;
pushStyle();
if (beat==cur_beat) {
grid.fill(255,0,0);
wave(beat,note);
} else {
grid.fill(255);
}
grid.rect(x,y, box_size, box_size);
popStyle();
}
}
}
}
void wave(int beat, int note){
int x = box_spacing*beat+x_margin;
int y = box_spacing*note+y_margin;
squareWave(x,y,.1,box_size);
}
void nextEnsemble(){
if (ensemble < instruments.length-1){
ensemble++;
} else {
ensemble = 0;
}
}
// populate the notes array with Perlin sequences and make the soundcipher score
void makeMusic(){
noLoop();
noiseSeed(frameCount);
float note_density = 0.2;
notes = new boolean[beats][NOTES];
for (int part=0;part<4;part++){
for (int beat=0; beat<beats; beat++){
int note = (int) (noise(beat,part)*NOTES);
if (random(1)<note_density) notes[beat][note] = true;
}
}
makeScore();
loop();
}
void makeScore(){
initializeScore();
for (int beat=0; beat<beats; beat++){
for (int note=0; note<NOTES; note++){
if (notes[beat][note]) {
addToScore(beat, note);
}
}
}
score.update();
score.play(-1);
}
void initializeScore(){
score.stop();
score.empty();
score.tempo(TEMPO);
score.instrument(sc.instrument);
// anchor start and end
score.addNote(0,0,0,1);
score.addNote(beats,0,0,1);
// callback eventIDs keep track of the beat
for (int i=0; i<=beats; i++){
score.addCallback(i, i);
}
played = false;
}
void addToScore(int beat, int note){
int pitch = (int)MIDDLE_C - note + MIDDLE_C_POS;
int channel = (int)random(instruments[ensemble].length);
float instrument = instruments[ensemble][channel];
int pan = (int) (((float)beat/((float)beats))*127);
//instruments must be on separate channels or only one note is played in each beat
score.addNote(beat,channel, instrument, pitch, 120,1,0.8,pan);
}
Water Music - Dec 2009
by volts (Tom Blackwell)
Left mouse generates a new score.
Right mouse changes instruments.
The picture_pallet.pde and ripples.pde utility sketches are easily used in other sketches.
Thanks to Andrew R. Brown for the SoundCipher library.