/**
* Drag the mouse over the pattern sliders to create sound.
* Add or remove sliders via the "steps" control.
*
Try setting the number of steps to an interesting metrical unit — say "8" —
* and use the pattern as a fractal step sequencer.
*
If the UI freezes, try reloading the page.
*
If you like the sounds, you can download and run the app locally to save out files.
*
Mac OS X version
* Windows version
* Linux version
* Back to Raintone.com
*
*
Thanks to Terran Olson's work on audio fractals that inspired this sketch.
* Thanks to Krister Olsson's Ess library for the sound support.
*
*/
// Fractal Wavetables
// March 2009
// jdn (at) raintone.com
//
// change history:
// v1 - basic algorithm and mono saving
// v2 - added drag-and-drop import of fw_xxxx.aif files
// v3 - code cleanup
//
//
import krister.Ess.*; // nice simple sound library
import sojamo.drop.*;
/*
* Model
*/
// Important constants
int SR = 44100;
int maxSliders = 80;
boolean canSave = false; // set to true to run this locally and write files
// fractal data
FloatFract[] fracts = new FloatFract[3]; // left, right, and l->r morph versions
FloatFract fract = fracts[0];
// pattern size / timing data
int curNumSliders = 0;
float curTargetLength = 0;
int targetIteration = 0;
// audio system
AudioChannel mySound;
boolean waveDirty = true;
boolean audioPlaying = true;
SDrop drop; // for dropping saved files back into the program to load as "presets"
/*
* Control
*/
void setup() {
Ess.start(this);
size(800, 600);
colorMode(HSB, 1);
background(0);
drop = new SDrop(this);
fract = new FloatFract();
createGUI();
updatePattern();
}
public void stop() {
Ess.stop();
super.stop();
}
public void draw() {
drawGUI();
// process any updates
if(fract.iteration() < targetIteration) {
fract.iterate();
}
else if (waveDirty && audioPlaying) {
stopAudio();
writeAudio();
playAudio();
}
}
void mousePressed() {
if(canSave) {
if(saveButton.pressed()) {
doSave();
}
}
}
void mouseDragged() {
if(numSlidersSlider.mouseOver()) {
checkNumSliders();
} else if (targetLengthSlider.mouseOver()) {
drawTargetLengthIndicator();
}
}
void mouseReleased() {
checkNumSliders();
checkTargetLength();
updatePattern();
}
void dropEvent(DropEvent theDropEvent) {
if(theDropEvent.isFile()) {
// for further information see
// http://java.sun.com/j2se/1.4.2/docs/api/java/io/File.html
File myFile = theDropEvent.file();
if(myFile.isFile()) {
// attempt to parse filename
String fileName = myFile.getName();
doRestore(fileName);
}
}
}
/*
* Control Helper Functions
*/
void doSave() {
stopAudio();
String defaultFilename = "fw_"+fract.toString();
if(defaultFilename.length() > 250) {
defaultFilename = defaultFilename.substring(0, 250);
}
defaultFilename += ".aif";
String path = "" + defaultFilename;
print("Save path: " + path + "\n");
writeSoundFile(path);
playAudio();
}
void doRestore(String fileName) {
if(fileName.endsWith(".aif") && fileName.startsWith("fw_")) {
// looks OK-ish
fileName = fileName.substring(3, fileName.length()-4); // strip pre- and suffix
//print("restoring seed:" + fileName);
stopAudio();
invalidateAudio();
clearDisplay();
FloatFract newFract = new FloatFract(fileName);
// copy seed onto UI sliders
numSlidersSlider.setValue(newFract.pattern().size());
checkNumSliders();
checkTargetLength();
for(int i = 0; i < curNumSliders; i++) {
sliders[i].setValue(((Double)newFract.pattern().get(i)).floatValue());
}
updatePattern();
drawSlidersIndicator();
playAudio();
}
}
void checkNumSliders() {
if(curNumSliders != desiredNumSliders()) {
stopAudio();
setupSliders(desiredNumSliders());
Runtime.getRuntime().gc();
}
}
void checkTargetLength() {
if(curTargetLength != targetLengthSlider.value()) {
curTargetLength = targetLengthSlider.value();
drawTargetLengthIndicator();
if(targetIteration != calculateIterationBounds())
waveDirty = true;
}
}
int desiredNumSliders() {
return numSlidersSlider.intValue();
}
void updatePattern() {
ArrayList newPattern = new ArrayList(curNumSliders);
for(int i = 0; i < curNumSliders; i++) {
newPattern.add(new Double(sliders[i].value()));
}
if(waveDirty || (!patternsSame(newPattern, fract.pattern()))) {
targetIteration = calculateIterationBounds();
fract.setPattern(newPattern);
invalidateAudio();
clearDisplay();
Runtime.getRuntime().gc();
playAudio();
}
}
int calculateIterationBounds() {
// numSliders^iteration = total samples
float targetLength = targetLengthSlider.value();
float numIterations = log(SR*targetLength) / log(curNumSliders);
int targetIteration = ceil(numIterations); // favor longer clips
// unless it would be too long
if (pow(curNumSliders, targetIteration) >= SR*targetLength*4)
targetIteration--;
return targetIteration;
}