You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Trying to implement a circular buffer that holds (and displays the waveform for) the last k seconds of input from audioIn. I started on a version with java's ArrayDeque, but I'm having trouble getting the data into a continuously scrolling waveform (events in the waveform would move from right to left as they move into the past). Any suggestions or examples I might check would be great.. thanks!
An interesting problem! You don't even need to create your own circular buffer, you can just use the Waveform class to create one that's large enough for the number of seconds you want to display, and then selectively read from that large array. Below is some working code for you, note how it might look like there are artefacts (samples jumping up and down as they're scrolling along) but that is simply an effect of being unable to draw every sample all the time -- you would need to draw 44 thousands pixels for every second of audio. If you want to smooth out the graphics you might have to do something like averaging over all the adjacent samples that go into a pixel...
import processing.sound.*;
AudioIn input;
Waveform waveform;
void setup() {
size(640, 480);
noStroke();
input = new AudioIn(this, 0);
int numberOfSecondsToView = 3;
int numberOfSamplesToView = numberOfSecondsToView * new Sound(this).sampleRate();
waveform = new Waveform(this, numberOfSamplesToView);
waveform.input(input);
}
void draw() {
background(125, 255, 125);
fill(255, 0, 150);
// get the most recent samples
float[] samples = waveform.analyze();
// only draw a subset of the samples
for (int i = 0; i < width; i++) {
// map the pixel position in [0,width - 1] to samples array
// indices in [0,samples.length - 1]
int index = (int) map(i, 0, width-1, 0, samples.length - 1);
// for every pixeldraw a rectangle up or down from the mid-point (the height can be 'negative'!)
rect(i, height / 2, 1, map(samples[index], -1, 1, -height/2, height/2));
}
}
Nice! I tried your averaging suggestion, dividing the width into buckets and averaging the sample values for each, but found the negative and positive values cancel each other out, so below uses the max absolute value of the samples in a bucket:
background(245);
float[] samples = waveform.analyze();
for (int i = 0; i < width; i++) {
values[i] = 0;
for (int j = 0; j < samplesPerPixel; j++) {
float val = abs(samples[i * samplesPerPixel + j]);
if (val > values[i]) values[i] = val;
}
}
for (int i = 0; i < width; i++) {
float h = values[i] * height;
rect(i, height / 2 - h / 2, 1, h);
}
The problem is that the Processing millis() might not be perfectly in sync with the synthesis engine, so that's not a reliable way to determine how many of the samples in the waveform buffer are new in comparison to the last call. This information (number of new frames) is of course available in the underlying synthesis engine, there's just no way to access it from the current Sound library release.
I added some new functions to the library and uploaded a test build here, just extract the zip in your Documents/Arduino/libraries folder and you should be good to go: sound.zip
The main method of interest for you is:
int waveform.getLastAnalysisOffset() -- after calling analyze(), the int returned by this function (in the range [0, nsamples-1]) will tell you the offset into the underlying circular buffer from which the first element of the float[] array returned by analyze() is taken. The absolute value of this is not relevant for you, but you can calculate exactly how many new samples have been added since the last call by taking the difference between the offsets after each call!
I've also added two more methods that allow you to view the current state of the underlying circular buffer directly, namely:
float[] waveform.analyzeCircular()
float[] waveform.analyzeCircular(float[] target)
The Waveform class will continuously overwrite data in the array, so if you want to find out where the newest data has just been written to you can use the new waveform.getLastAnalysisOffset() method from above, and actually use its absolute return value to access the most recent data. (You don't have to use this information, just replace analyze() with analyzeCircular() in the code I posted above and you should already get good results!)
Hope that helps, let me know if you find anything wrong with the test build!
So running the following code in draw (with a 1-sec buffer) , I get the following number of (what I think should be) new samples for each of the first 100 frames (sometimes 0, occasionally negative) -- does this look correct? I guess I would need to handle the wraparounds, but the zeros are still perplexing.
public void draw()
{
float[] samples = waveform.analyze();
int offset = waveform.getLastAnalysisOffset();
int newSamples = offset - lastOffset;
println(frameCount, newSamples);
...
lastOffset = offset;
}
@kevinstadler trying to get this to work again, but waveform.getLastAnalysisOffset seems to have been removed from the API... any ideas on how to proceed?
Activity
[-]Circular buffer using audioIn[/-][+]Circular buffer with audioIn[/+]kevinstadler commentedon May 2, 2020
An interesting problem! You don't even need to create your own circular buffer, you can just use the
Waveformclass to create one that's large enough for the number of seconds you want to display, and then selectively read from that large array. Below is some working code for you, note how it might look like there are artefacts (samples jumping up and down as they're scrolling along) but that is simply an effect of being unable to draw every sample all the time -- you would need to draw 44 thousands pixels for every second of audio. If you want to smooth out the graphics you might have to do something like averaging over all the adjacent samples that go into a pixel...dhowe commentedon May 3, 2020
Nice! I tried your averaging suggestion, dividing the width into buckets and averaging the sample values for each, but found the negative and positive values cancel each other out, so below uses the max absolute value of the samples in a bucket:
[-]Circular buffer with audioIn[/-][+]Circular buffer with AudioIn[/+]dhowe commentedon May 4, 2020
This full version uses the same method, but attempts to only update the new pixels. Still doesn't seem quite right to me though. Any thoughts?
kevinstadler commentedon May 5, 2020
The problem is that the Processing
millis()might not be perfectly in sync with the synthesis engine, so that's not a reliable way to determine how many of the samples in the waveform buffer are new in comparison to the last call. This information (number of new frames) is of course available in the underlying synthesis engine, there's just no way to access it from the current Sound library release.I added some new functions to the library and uploaded a test build here, just extract the zip in your
Documents/Arduino/librariesfolder and you should be good to go: sound.zipThe main method of interest for you is:
int waveform.getLastAnalysisOffset()-- after callinganalyze(), theintreturned by this function (in the range[0, nsamples-1]) will tell you the offset into the underlying circular buffer from which the first element of thefloat[]array returned byanalyze()is taken. The absolute value of this is not relevant for you, but you can calculate exactly how many new samples have been added since the last call by taking the difference between the offsets after each call!I've also added two more methods that allow you to view the current state of the underlying circular buffer directly, namely:
float[] waveform.analyzeCircular()float[] waveform.analyzeCircular(float[] target)The Waveform class will continuously overwrite data in the array, so if you want to find out where the newest data has just been written to you can use the new
waveform.getLastAnalysisOffset()method from above, and actually use its absolute return value to access the most recent data. (You don't have to use this information, just replaceanalyze()withanalyzeCircular()in the code I posted above and you should already get good results!)Hope that helps, let me know if you find anything wrong with the test build!
dhowe commentedon May 5, 2020
So running the following code in draw (with a 1-sec buffer) , I get the following number of (what I think should be) new samples for each of the first 100 frames (sometimes 0, occasionally negative) -- does this look correct? I guess I would need to handle the wraparounds, but the zeros are still perplexing.
dhowe commentedon May 5, 2020
Getting closer...
kevinstadler commentedon Sep 16, 2023
This will be addressed as a special case of #55
dhowe commentedon Nov 22, 2025
@kevinstadler trying to get this to work again, but
waveform.getLastAnalysisOffsetseems to have been removed from the API... any ideas on how to proceed?