diff --git a/examples/Analysis/AudioWaveform/AudioWaveform.pde b/examples/Analysis/AudioWaveform/AudioWaveform.pde new file mode 100644 index 0000000..7cd6027 --- /dev/null +++ b/examples/Analysis/AudioWaveform/AudioWaveform.pde @@ -0,0 +1,48 @@ +/** + * This sketch shows how to use the Waveform class to analyze a stream + * of sound. Change the number of samples to get a longer/shorter part of the waveform. + */ + +import processing.sound.*; + +// Declare the sound source and Waveform analyzer variables +SoundFile sample; +Waveform waveform; + +// Define how many samples of the Waveform you want to use (must be a power of two) +int samples = 128; + +public void setup() { + size(640, 360); + background(255); + + // Load and play a soundfile and loop it. + sample = new SoundFile(this, "beat.aiff"); + sample.loop(); + + // Create the Waveform analyzer and connect the playing soundfile to it. + waveform = new Waveform(this, samples); + waveform.input(sample); +} + +public void draw() { + // Set background color, noFill and stroke style + background(0); + stroke(255); + strokeWeight(2); + noFill(); + + // Perform the analysis + waveform.analyze(); + + beginShape(); + for(int i = 0; i < samples; i++){ + // Draw current data of the waveform + // Each sample in the data array is between -1 and +1 + vertex( + map(i, 0, samples, 0, width), + map(waveform.data[i], -1, 1, 0, height) + ); + } + endShape(); +} diff --git a/examples/Analysis/AudioWaveform/data/beat.aiff b/examples/Analysis/AudioWaveform/data/beat.aiff new file mode 100644 index 0000000..017b7ce Binary files /dev/null and b/examples/Analysis/AudioWaveform/data/beat.aiff differ diff --git a/src/processing/sound/JSynWaveform.java b/src/processing/sound/JSynWaveform.java new file mode 100644 index 0000000..0534354 --- /dev/null +++ b/src/processing/sound/JSynWaveform.java @@ -0,0 +1,32 @@ +package processing.sound; + +import java.util.Arrays; + +import com.jsyn.data.FloatSample; +import com.jsyn.unitgen.FixedRateMonoWriter; + +/** + * This class copies all input to an audio buffer of the given size and returns it. + * + * @author icalvin102 + */ +class JSynWaveform extends FixedRateMonoWriter { + + private FloatSample buffer; + + protected JSynWaveform(int bufferSize) { + super(); + this.buffer = new FloatSample(bufferSize); + + // write any connected input into the output buffer ad infinitum + this.dataQueue.queueLoop(this.buffer); + } + + protected void calculateWaveform(float[] target) { + // get position currently being written to + int pos = (int) this.dataQueue.getFrameCount() % this.buffer.getNumFrames(); + for (int i = 0; i < this.buffer.getNumFrames(); i++) { + target[i] = (float)(2*this.buffer.readDouble((pos + i) % this.buffer.getNumFrames())); + } + } +} diff --git a/src/processing/sound/Waveform.java b/src/processing/sound/Waveform.java new file mode 100644 index 0000000..dae5dbd --- /dev/null +++ b/src/processing/sound/Waveform.java @@ -0,0 +1,105 @@ +package processing.sound; + +import com.jsyn.ports.UnitOutputPort; + +import processing.core.PApplet; + +/** + * This is a Waveform analyzer. It returns the waveform of the + * of an audio stream the moment it is queried with the analyze() + * method. + * + * @author icalvin102 + * + * @webref sound + **/ +public class Waveform extends Analyzer { + + public float[] data; + + private JSynWaveform waveform; + + public PApplet app; + + public Waveform(PApplet parent) { + this(parent, 512); + } + + /** + * @param parent + * typically use "this" + * @param samples + * number of waveform samples as an integer (default 512). + * This parameter needs to be a power of 2 (e.g. 16, 32, 64, 128, + * ...). + * @webref sound + */ + public Waveform(PApplet parent, int samples) { + super(parent); + app = parent; + if (samples < 0 || Integer.bitCount(samples) != 1) { + // TODO throw RuntimeException? + Engine.printError("number of waveform frames needs to be a power of 2"); + } else { + // FFT buffer size is twice the number of frequency bands + this.waveform = new JSynWaveform(samples); + this.data = new float[samples]; + } + } + + protected void removeInput() { + this.waveform.input.disconnectAll(); + this.input = null; + } + + protected void setInput(UnitOutputPort input) { + // superclass makes sure that input unit is actually playing, just connect it + Engine.getEngine().add(this.waveform); + this.waveform.input.connect(input); + this.waveform.start(); + } + + /** + * Gets the content of the current audiobuffer from the input source, writes it + * into this Waveform's `data` array, and returns it. + * + * @return the current audiobuffer of the input source. The array has as + * many elements as this Waveform analyzer's number of samples + */ + public float[] analyze() { + return this.analyze(this.data); + } + + /** + * Gets the content of the current audiobuffer from the input source. + * + * @param value + * an array with as many elements as this Waveform analyzer's number of + * samples + * @return the current audiobuffer of the input source. The array has as + * many elements as this Waveform analyzer's number of samples + * @webref sound + **/ + public float[] analyze(float[] value) { + if (this.input == null) { + Engine.printWarning("this Waveform has no sound source connected to it, nothing to analyze"); + } + this.waveform.calculateWaveform(value); + return value; + } + + // Below are just duplicated methods from superclasses which are required + // for the online reference to build the corresponding pages. + + /** + * Define the audio input for the analyzer. + * + * @param input + * the input sound source. Can be an oscillator, noise generator, + * SoundFile or AudioIn. + * @webref sound + **/ + public void input(SoundObject input) { + super.input(input); + } +}