Skip to content

Commit 97e49c2

Browse files
authored
Merge pull request #147 from mkontogiannis/linlog-averages
Linlog averages
2 parents b1781bf + e56b20b commit 97e49c2

File tree

6 files changed

+343
-5
lines changed

6 files changed

+343
-5
lines changed

examples/FFT_linAverages/index.html

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<head>
2+
<script language="javascript" type="text/javascript" src="../../lib/p5.js"></script>
3+
4+
<script language="javascript" type="text/javascript" src="../../lib/addons/p5.dom.js"></script>
5+
6+
<script language="javascript" type="text/javascript" src="../../lib/p5.sound.min.js"></script>
7+
8+
<script language="javascript" type="text/javascript" src="sketch.js"></script>
9+
10+
</head>

examples/FFT_linAverages/sketch.js

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
/**
2+
* Display the average amount of amplitude across a range
3+
* of frequencies using the p5.FFT class and its methods analyze()
4+
* and linAverages().
5+
*
6+
* This example divides the frequency spectrum into eleven bands of equal frequency range.
7+
*/
8+
9+
var soundFile;
10+
var fft;
11+
12+
var description = 'loading';
13+
var p;
14+
var barsNumber;
15+
16+
function preload() {
17+
soundFormats('mp3', 'ogg');
18+
soundFile = loadSound('../files/lucky_dragons_-_power_melody');
19+
}
20+
21+
function setup() {
22+
createCanvas(1024, 400);
23+
fill(255, 40, 255);
24+
noStroke();
25+
textAlign(CENTER);
26+
27+
fft = new p5.FFT();
28+
29+
p = createP(description);
30+
var p2 = createP('Description: Using linAverages() to produce the averages for 11 groups of equal frequency range.');
31+
32+
barsNumber = 11; // Set this to 11 so it easier to compare with the LogAverages example
33+
}
34+
35+
function draw() {
36+
background(30,20,30);
37+
updateDescription();
38+
39+
fft.analyze(); // analyze before calling fft.linAverages()
40+
var groupedFrequencies = fft.linAverages(barsNumber);
41+
42+
// Generate the bars to represent the different frequency averages per group
43+
for (var i = 0; i < barsNumber; i++){
44+
noStroke();
45+
fill((i * 30) % 100 + 50, 195, ((i * 25) + 50) % 255);
46+
47+
// Rectangle height represents the average value of this frequency range
48+
var h = -height + map(groupedFrequencies[i], 0, 255, height, 0);
49+
rect(((i+1) * width / barsNumber) - width/barsNumber, height, width/barsNumber, h);
50+
51+
fill(255);
52+
var loFreq = calcFreqFromIndex(i * barsNumber);
53+
var hiFreq = calcFreqFromIndex((i + 1) * barsNumber);
54+
text(loFreq.toFixed(0) +'-'
55+
+ hiFreq.toFixed(0)+' Hz', (i+1) * width / barsNumber - width / barsNumber / 2, 30);
56+
}
57+
}
58+
59+
// Helper function for drawing the group ranges
60+
function calcFreqFromIndex(index) {
61+
var nyquist = sampleRate() / 2;
62+
var indexFrequency = Math.round((index * nyquist) / fft.bins);
63+
64+
return (indexFrequency);
65+
}
66+
67+
function keyPressed() {
68+
if (soundFile.isPlaying()){
69+
soundFile.pause();
70+
} else {
71+
soundFile.loop();
72+
}
73+
}
74+
75+
// Change description text if the song is loading, playing or paused
76+
function updateDescription() {
77+
if (!soundFile.isPlaying()) {
78+
description = 'Paused...';
79+
p.html(description);
80+
}
81+
else if (soundFile.isPlaying()){
82+
description = 'Playing!';
83+
p.html(description);
84+
}
85+
else {
86+
for (var i = 0; i < frameCount%3; i++ ) {
87+
88+
// add periods to loading to create a fun loading bar effect
89+
if (frameCount%4 == 0){
90+
description += '.';
91+
}
92+
if (frameCount%25 == 0) {
93+
description = 'loading';
94+
95+
}
96+
}
97+
p.html(description);
98+
}
99+
}

examples/FFT_logAverages/index.html

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<head>
2+
<script language="javascript" type="text/javascript" src="../../lib/p5.js"></script>
3+
4+
<script language="javascript" type="text/javascript" src="../../lib/addons/p5.dom.js"></script>
5+
6+
<script language="javascript" type="text/javascript" src="../../lib/p5.sound.min.js"></script>
7+
8+
<script language="javascript" type="text/javascript" src="sketch.js"></script>
9+
10+
</head>

examples/FFT_logAverages/sketch.js

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
/**
2+
* Display the average amount of amplitude across a range
3+
* of frequencies using the p5.FFT class and its methods analyze()
4+
* and logAverages().
5+
*
6+
* This example divides the frequency spectrum into the first 11 octave bands.
7+
* https://courses.physics.illinois.edu/phys193/labs/octave_bands.pdf
8+
*/
9+
10+
var soundFile;
11+
var fft;
12+
13+
var description = 'loading';
14+
var p;
15+
var barsNumber;
16+
var octaveBands;
17+
18+
function preload() {
19+
soundFormats('mp3', 'ogg');
20+
soundFile = loadSound('../files/lucky_dragons_-_power_melody');
21+
}
22+
23+
function setup() {
24+
createCanvas(1024, 400);
25+
fill(255, 40, 255);
26+
noStroke();
27+
textAlign(CENTER);
28+
29+
fft = new p5.FFT();
30+
31+
p = createP(description);
32+
var p2 = createP('Description: Using logAverages() to produce the average amplitude \
33+
of the 11 octave bands.');
34+
35+
octaveBands = fft.getOctaveBands(1);
36+
barsNumber = octaveBands.length;
37+
}
38+
39+
function draw() {
40+
background(30,20,30);
41+
updateDescription();
42+
43+
fft.analyze(); // analyze before calling fft.logAverages()
44+
var groupedFrequencies = fft.logAverages(octaveBands);
45+
46+
// Generate the bars to represent the different frequency averages per group
47+
for (var i = 0; i < barsNumber; i++){
48+
noStroke();
49+
fill((i * 30) % 100 + 50, 195, ((i * 25) + 50) % 255);
50+
51+
// Rectangle height represents the average value of this frequency range
52+
var h = -height + map(groupedFrequencies[i], 0, 255, height, 0);
53+
rect(((i+1) * width / barsNumber) - width/barsNumber, height, width/barsNumber, h);
54+
55+
fill(255);
56+
var loFreq = octaveBands[i].lo;
57+
var hiFreq = octaveBands[i].hi;
58+
text(loFreq.toFixed(0) +'-'
59+
+ hiFreq.toFixed(0)+' Hz', (i+1) * width / barsNumber - width / barsNumber / 2, 30);
60+
}
61+
}
62+
63+
// Helper function for drawing the group ranges
64+
function calcFreqFromIndex(index) {
65+
var nyquist = sampleRate() / 2;
66+
var indexFrequency = Math.round((index * nyquist) / fft.bins);
67+
68+
return (indexFrequency);
69+
}
70+
71+
function keyPressed() {
72+
if (soundFile.isPlaying()){
73+
soundFile.pause();
74+
} else {
75+
soundFile.loop();
76+
}
77+
}
78+
79+
// Change description text if the song is loading, playing or paused
80+
function updateDescription() {
81+
if (!soundFile.isPlaying()) {
82+
description = 'Paused...';
83+
p.html(description);
84+
}
85+
else if (soundFile.isPlaying()){
86+
description = 'Playing!';
87+
p.html(description);
88+
}
89+
else {
90+
for (var i = 0; i < frameCount%3; i++ ) {
91+
92+
// add periods to loading to create a fun loading bar effect
93+
if (frameCount%4 == 0){
94+
description += '.';
95+
}
96+
if (frameCount%25 == 0) {
97+
description = 'loading';
98+
99+
}
100+
}
101+
p.html(description);
102+
}
103+
}

index.html

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ <h2>p5.sound
88
~ <a href="http://github.com/processing/p5.js-sound">Source</a></h2><h3>Examples:</h3><div><a href="examples/DelayNoiseEnvelope">DelayNoiseEnvelope</a></div>
99
<div><a href="examples/FFT_freqRange">FFT_freqRange</a></div>
1010
<div><a href="examples/FFT_frequency_spectrum">FFT_frequency_spectrum</a></div>
11+
<div><a href="examples/FFT_linAverages">FFT_linAverages</a></div>
12+
<div><a href="examples/FFT_logAverages">FFT_logAverages</a></div>
1113
<div><a href="examples/FFT_scaleNeighbors">FFT_scaleNeighbors</a></div>
1214
<div><a href="examples/FFT_scaleOneThirdOctave">FFT_scaleOneThirdOctave</a></div>
1315
<div><a href="examples/Filter_BandPass">Filter_BandPass</a></div>

src/fft.js

Lines changed: 119 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -369,9 +369,7 @@ define(function (require) {
369369
console.log('getFreq() is deprecated. Please use getEnergy() instead.');
370370
var x = this.getEnergy(freq1, freq2);
371371
return x;
372-
}
373-
374-
372+
};
375373

376374
/**
377375
* Returns the
@@ -459,7 +457,7 @@ define(function (require) {
459457

460458
var spec_centroid_freq = (mean_freq_index * (nyquist / this.freqDomain.length));
461459
return spec_centroid_freq;
462-
}
460+
};
463461

464462
/**
465463
* Smooth FFT analysis by averaging with the last analysis frame.
@@ -482,7 +480,123 @@ define(function (require) {
482480

483481
this.analyser.disconnect();
484482
this.analyser = undefined;
485-
}
483+
};
484+
485+
/**
486+
* Returns an array of average amplitude values for a given number
487+
* of frequency bands split equally. N defaults to 16.
488+
* <em>NOTE: analyze() must be called prior to linAverages(). Analyze()
489+
* tells the FFT to analyze frequency data, and linAverages() uses
490+
* the results to group them into a smaller set of averages.</em></p>
491+
*
492+
* @method linAverages
493+
* @param {Number} N Number of returned frequency groups
494+
* @return {Array} linearAverages Array of average amplitude values for each group
495+
*/
496+
p5.FFT.prototype.linAverages = function(N) {
497+
var N = N || 16; // This prevents undefined, null or 0 values of N
498+
499+
var spectrum = this.freqDomain;
500+
var spectrumLength = spectrum.length;
501+
var spectrumStep = Math.floor(spectrumLength / N);
502+
503+
var linearAverages = new Array(N);
504+
// Keep a second index for the current average group and place the values accordingly
505+
// with only one loop in the spectrum data
506+
var groupIndex = 0;
507+
508+
for (var specIndex = 0; specIndex < spectrumLength; specIndex++) {
509+
510+
linearAverages[groupIndex] = (linearAverages[groupIndex] !== undefined)
511+
? (linearAverages[groupIndex] + spectrum[specIndex]) / 2
512+
: spectrum[specIndex];
513+
514+
// Increase the group index when the last element of the group is processed
515+
if ((specIndex % spectrumStep) == (spectrumStep - 1)) {
516+
groupIndex++;
517+
}
518+
}
519+
520+
return (linearAverages);
521+
};
522+
523+
/**
524+
* Returns an array of average amplitude values of the spectrum, for a given
525+
* set of <a href="https://en.wikipedia.org/wiki/Octave_band" target="_blank">
526+
* Octave Bands</a>
527+
* <em>NOTE: analyze() must be called prior to logAverages(). Analyze()
528+
* tells the FFT to analyze frequency data, and logAverages() uses
529+
* the results to group them into a smaller set of averages.</em></p>
530+
*
531+
* @method logAverages
532+
* @param {Array} octaveBands Array of Octave Bands objects for grouping
533+
* @return {Array} logAverages Array of average amplitude values for each group
534+
*/
535+
p5.FFT.prototype.logAverages = function(octaveBands) {
536+
var nyquist = p5sound.audiocontext.sampleRate / 2;
537+
var spectrum = this.freqDomain;
538+
var spectrumLength = spectrum.length;
539+
540+
var logAverages = new Array(octaveBands.length);
541+
// Keep a second index for the current average group and place the values accordingly
542+
// With only one loop in the spectrum data
543+
var octaveIndex = 0;
544+
545+
for (var specIndex = 0; specIndex < spectrumLength; specIndex++) {
546+
var specIndexFrequency = Math.round((specIndex * nyquist) / this.freqDomain.length);
547+
548+
// Increase the group index if the current frequency exceeds the limits of the band
549+
if (specIndexFrequency > octaveBands[octaveIndex].hi) {
550+
octaveIndex++;
551+
}
552+
553+
logAverages[octaveIndex] = (logAverages[octaveIndex] !== undefined)
554+
? (logAverages[octaveIndex] + spectrum[specIndex]) / 2
555+
: spectrum[specIndex];
556+
}
557+
558+
return (logAverages);
559+
};
560+
561+
/**
562+
* Calculates and Returns the 1/N
563+
* <a href="https://en.wikipedia.org/wiki/Octave_band" target="_blank">Octave Bands</a>
564+
* N defaults to 3 and minimum central frequency to 15.625Hz.
565+
* (1/3 Octave Bands ~= 31 Frequency Bands)
566+
* Setting fCtr0 to a central value of a higher octave will ignore the lower bands
567+
* and produce less frequency groups.
568+
*
569+
* @method getOctaveBands
570+
* @param {Number} N Specifies the 1/N type of generated octave bands
571+
* @param {Number} fCtr0 Minimum central frequency for the lowest band
572+
* @return {Array} octaveBands Array of octave band objects with their bounds
573+
*/
574+
p5.FFT.prototype.getOctaveBands = function(N, fCtr0) {
575+
var N = N || 3; // Default to 1/3 Octave Bands
576+
var fCtr0 = fCtr0 || 15.625; // Minimum central frequency, defaults to 15.625Hz
577+
578+
var octaveBands = [];
579+
var lastFrequencyBand = {
580+
lo: fCtr0 / Math.pow(2, 1 / (2*N)),
581+
ctr: fCtr0,
582+
hi: fCtr0 * Math.pow(2, 1 / (2*N)),
583+
};
584+
octaveBands.push(lastFrequencyBand);
585+
586+
var nyquist = p5sound.audiocontext.sampleRate / 2;
587+
while (lastFrequencyBand.hi < nyquist) {
588+
589+
var newFrequencyBand = {};
590+
newFrequencyBand.lo = lastFrequencyBand.hi,
591+
newFrequencyBand.ctr = lastFrequencyBand.ctr * Math.pow(2, 1 / N),
592+
newFrequencyBand.hi = newFrequencyBand.ctr * Math.pow(2, 1 / (2*N)),
593+
594+
octaveBands.push(newFrequencyBand);
595+
lastFrequencyBand = newFrequencyBand;
596+
}
597+
598+
return (octaveBands);
599+
};
486600

487601
// helper methods to convert type from float (dB) to int (0-255)
488602
var freqToFloat = function (fft) {

0 commit comments

Comments
 (0)