Skip to content

Commit 0dbcfef

Browse files
authored
deprecate p5.SoundFile.processPeaks (#584)
this offline approach to processing peaks didn't work very well and makes the codebase confusing!
1 parent ea77cb8 commit 0dbcfef

File tree

3 files changed

+1
-304
lines changed

3 files changed

+1
-304
lines changed

examples/peakDetection_offline/index.html

Lines changed: 0 additions & 8 deletions
This file was deleted.

examples/peakDetection_offline/sketch.js

Lines changed: 0 additions & 47 deletions
This file was deleted.

src/soundfile.js

Lines changed: 1 addition & 249 deletions
Original file line numberDiff line numberDiff line change
@@ -16,161 +16,6 @@ var _createCounterBuffer = function (buffer) {
1616
return audioBuf;
1717
};
1818

19-
// process peaks
20-
class Peak {
21-
constructor(amp, i) {
22-
this.sampleIndex = i;
23-
this.amplitude = amp;
24-
this.tempos = [];
25-
this.intervals = [];
26-
}
27-
}
28-
29-
// 1. for processPeaks() Function to identify peaks above a threshold
30-
// returns an array of peak indexes as frames (samples) of the original soundfile
31-
function getPeaksAtThreshold(data, threshold) {
32-
var peaksObj = {};
33-
var length = data.length;
34-
35-
for (var i = 0; i < length; i++) {
36-
if (data[i] > threshold) {
37-
var amp = data[i];
38-
var peak = new Peak(amp, i);
39-
peaksObj[i] = peak;
40-
// Skip forward ~ 1/8s to get past this peak.
41-
i += 6000;
42-
}
43-
i++;
44-
}
45-
return peaksObj;
46-
}
47-
48-
// 2. for processPeaks()
49-
function countIntervalsBetweenNearbyPeaks(peaksObj) {
50-
var intervalCounts = [];
51-
var peaksArray = Object.keys(peaksObj).sort();
52-
53-
for (var index = 0; index < peaksArray.length; index++) {
54-
// find intervals in comparison to nearby peaks
55-
for (var i = 0; i < 10; i++) {
56-
var startPeak = peaksObj[peaksArray[index]];
57-
var endPeak = peaksObj[peaksArray[index + i]];
58-
59-
if (startPeak && endPeak) {
60-
var startPos = startPeak.sampleIndex;
61-
var endPos = endPeak.sampleIndex;
62-
var interval = endPos - startPos;
63-
64-
// add a sample interval to the startPeak in the allPeaks array
65-
if (interval > 0) {
66-
startPeak.intervals.push(interval);
67-
}
68-
69-
// tally the intervals and return interval counts
70-
var foundInterval = intervalCounts.some(function (intervalCount) {
71-
if (intervalCount.interval === interval) {
72-
intervalCount.count++;
73-
return intervalCount;
74-
}
75-
});
76-
77-
// store with JSON like formatting
78-
if (!foundInterval) {
79-
intervalCounts.push({
80-
interval: interval,
81-
count: 1,
82-
});
83-
}
84-
}
85-
}
86-
}
87-
88-
return intervalCounts;
89-
}
90-
91-
// 3. for processPeaks --> find tempo
92-
function groupNeighborsByTempo(intervalCounts, sampleRate) {
93-
var tempoCounts = [];
94-
95-
intervalCounts.forEach(function (intervalCount) {
96-
try {
97-
// Convert an interval to tempo
98-
var theoreticalTempo = Math.abs(
99-
60 / (intervalCount.interval / sampleRate)
100-
);
101-
102-
theoreticalTempo = mapTempo(theoreticalTempo);
103-
104-
var foundTempo = tempoCounts.some(function (tempoCount) {
105-
if (tempoCount.tempo === theoreticalTempo)
106-
return (tempoCount.count += intervalCount.count);
107-
});
108-
if (!foundTempo) {
109-
if (isNaN(theoreticalTempo)) {
110-
return;
111-
}
112-
tempoCounts.push({
113-
tempo: Math.round(theoreticalTempo),
114-
count: intervalCount.count,
115-
});
116-
}
117-
} catch (e) {
118-
throw e;
119-
}
120-
});
121-
122-
return tempoCounts;
123-
}
124-
125-
// 4. for processPeaks - get peaks at top tempo
126-
function getPeaksAtTopTempo(peaksObj, tempo, sampleRate, bpmVariance) {
127-
var peaksAtTopTempo = [];
128-
var peaksArray = Object.keys(peaksObj).sort();
129-
130-
// TO DO: filter out peaks that have the tempo and return
131-
for (var i = 0; i < peaksArray.length; i++) {
132-
var key = peaksArray[i];
133-
var peak = peaksObj[key];
134-
135-
for (var j = 0; j < peak.intervals.length; j++) {
136-
var intervalBPM = Math.round(
137-
Math.abs(60 / (peak.intervals[j] / sampleRate))
138-
);
139-
140-
intervalBPM = mapTempo(intervalBPM);
141-
142-
if (Math.abs(intervalBPM - tempo) < bpmVariance) {
143-
// convert sampleIndex to seconds
144-
peaksAtTopTempo.push(peak.sampleIndex / sampleRate);
145-
}
146-
}
147-
}
148-
149-
// filter out peaks that are very close to each other
150-
peaksAtTopTempo = peaksAtTopTempo.filter(function (peakTime, index, arr) {
151-
var dif = arr[index + 1] - peakTime;
152-
if (dif > 0.01) {
153-
return true;
154-
}
155-
});
156-
157-
return peaksAtTopTempo;
158-
}
159-
160-
// helper function for processPeaks
161-
function mapTempo(theoreticalTempo) {
162-
// these scenarios create infinite while loop
163-
if (!isFinite(theoreticalTempo) || theoreticalTempo === 0) {
164-
return;
165-
}
166-
167-
// Adjust the tempo to fit within the 90-180 BPM range
168-
while (theoreticalTempo < 90) theoreticalTempo *= 2;
169-
while (theoreticalTempo > 180 && theoreticalTempo > 90) theoreticalTempo /= 2;
170-
171-
return theoreticalTempo;
172-
}
173-
17419
/*** SCHEDULE EVENTS ***/
17520

17621
// Cue inspired by JavaScript setTimeout, and the
@@ -1487,101 +1332,8 @@ class SoundFile {
14871332
return bufferSourceNode;
14881333
}
14891334

1490-
/**
1491-
* processPeaks returns an array of timestamps where it thinks there is a beat.
1492-
*
1493-
* This is an asynchronous function that processes the soundfile in an offline audio context,
1494-
* and sends the results to your callback function.
1495-
*
1496-
* The process involves running the soundfile through a lowpass filter, and finding all of the
1497-
* peaks above the initial threshold. If the total number of peaks are below the minimum number of peaks,
1498-
* it decreases the threshold and re-runs the analysis until either minPeaks or minThreshold are reached.
1499-
*
1500-
* @method processPeaks
1501-
* @for p5.SoundFile
1502-
* @param {Function} callback a function to call once this data is returned
1503-
* @param {Number} [initThreshold] initial threshold defaults to 0.9
1504-
* @param {Number} [minThreshold] minimum threshold defaults to 0.22
1505-
* @param {Number} [minPeaks] minimum number of peaks defaults to 200
1506-
* @return {Array} Array of timestamped peaks
1507-
*/
15081335
processPeaks(callback, _initThreshold, _minThreshold, _minPeaks) {
1509-
var self = this;
1510-
var bufLen = this.buffer.length;
1511-
var sampleRate = this.buffer.sampleRate;
1512-
var buffer = this.buffer;
1513-
var allPeaks = [];
1514-
1515-
var initialThreshold = _initThreshold || 0.9,
1516-
threshold = initialThreshold,
1517-
minThreshold = _minThreshold || 0.22,
1518-
minPeaks = _minPeaks || 200;
1519-
1520-
// Create offline context
1521-
var offlineContext = new window.OfflineAudioContext(1, bufLen, sampleRate);
1522-
1523-
// create buffer source
1524-
var source = offlineContext.createBufferSource();
1525-
source.buffer = buffer;
1526-
1527-
// Create filter. TO DO: allow custom setting of filter
1528-
var filter = offlineContext.createBiquadFilter();
1529-
filter.type = 'lowpass';
1530-
source.connect(filter);
1531-
filter.connect(offlineContext.destination);
1532-
1533-
// start playing at time:0
1534-
source.start(0);
1535-
offlineContext.startRendering(); // Render the song
1536-
1537-
// act on the result
1538-
offlineContext.oncomplete = function (e) {
1539-
if (!self.panner) return;
1540-
var filteredBuffer = e.renderedBuffer;
1541-
var bufferData = filteredBuffer.getChannelData(0);
1542-
1543-
// step 1:
1544-
// create Peak instances, add them to array, with strength and sampleIndex
1545-
do {
1546-
allPeaks = getPeaksAtThreshold(bufferData, threshold);
1547-
threshold -= 0.005;
1548-
} while (
1549-
Object.keys(allPeaks).length < minPeaks &&
1550-
threshold >= minThreshold
1551-
);
1552-
1553-
// step 2:
1554-
// find intervals for each peak in the sampleIndex, add tempos array
1555-
var intervalCounts = countIntervalsBetweenNearbyPeaks(allPeaks);
1556-
1557-
// step 3: find top tempos
1558-
var groups = groupNeighborsByTempo(
1559-
intervalCounts,
1560-
filteredBuffer.sampleRate
1561-
);
1562-
1563-
// sort top intervals
1564-
var topTempos = groups
1565-
.sort(function (intA, intB) {
1566-
return intB.count - intA.count;
1567-
})
1568-
.splice(0, 5);
1569-
1570-
// set this SoundFile's tempo to the top tempo ??
1571-
this.tempo = topTempos[0].tempo;
1572-
1573-
// step 4:
1574-
// new array of peaks at top tempo within a bpmVariance
1575-
var bpmVariance = 5;
1576-
var tempoPeaks = getPeaksAtTopTempo(
1577-
allPeaks,
1578-
topTempos[0].tempo,
1579-
filteredBuffer.sampleRate,
1580-
bpmVariance
1581-
);
1582-
1583-
callback(tempoPeaks);
1584-
};
1336+
console.warn('processPeaks is deprecated');
15851337
}
15861338

15871339
/**

0 commit comments

Comments
 (0)