Skip to content

Commit 037c593

Browse files
authored
Merge pull request #11 from eshaz/threads
Max Threads
2 parents 9845a7e + dffa5af commit 037c593

10 files changed

+356
-209
lines changed

demo/index.html

+127-15
Original file line numberDiff line numberDiff line change
@@ -406,6 +406,7 @@ <h4>Sync Multiple Clips</h4>
406406
* syncWorker: Executes in a Web Worker.
407407
* syncWorkerConcurrent: Executes in multiple Web Workers (multi-threaded).
408408
"
409+
id="correlation-method-row"
409410
>
410411
<td>
411412
<label for="correlation-method">Correlation Method</label>
@@ -420,6 +421,23 @@ <h4>Sync Multiple Clips</h4>
420421
</select>
421422
</td>
422423
</tr>
424+
<tr
425+
title="Selects number of threads to spawn when running concurrent operations"
426+
id="correlation-threads-row"
427+
>
428+
<td>
429+
<label for="correlation-threads">Correlation Threads</label>
430+
</td>
431+
<td>
432+
<input
433+
id="correlation-threads"
434+
name="correlationThreads"
435+
type="number"
436+
min="1"
437+
value="1"
438+
/>
439+
</td>
440+
</tr>
423441
<tr
424442
title="Selects the sample size in audio samples to compare when determining the best correlation.
425443
* This value can be increased for better accuracy, at the expense of execution time."
@@ -454,6 +472,26 @@ <h4>Sync Multiple Clips</h4>
454472
/>
455473
</td>
456474
</tr>
475+
<tr
476+
title="Threshold that will filter out any low correlation matches.
477+
* Only applicable to Multiple Clips (`syncMultiple`)"
478+
id="correlation-threshold-row"
479+
style="display: none"
480+
>
481+
<td>
482+
<label for="correlation-threshold">Correlation Threshold</label>
483+
</td>
484+
<td>
485+
<input
486+
id="correlation-threshold"
487+
name="correlationThreshold"
488+
type="number"
489+
value="0.5"
490+
min="0"
491+
max="1"
492+
/>
493+
</td>
494+
</tr>
457495
</table>
458496
</fieldset>
459497
<fieldset id="fft-options" class="column" style="flex-grow: 1">
@@ -503,11 +541,47 @@ <h4>Sync Multiple Clips</h4>
503541
const comparisonAudioClipKey = "comparison";
504542
const correlationAudioClipKey = "correlation";
505543

506-
const audioCtx = new AudioContext();
507-
audioCtx.onstatechange = () => {
508-
if (audioCtx !== "running") audioCtx.resume();
509-
};
510-
audioCtx.destination.channelCount = audioCtx.destination.maxChannelCount;
544+
const AudioContext = window.AudioContext || window.webkitAudioContext;
545+
let audioCtx;
546+
547+
// statically initialize audio context and start using a DOM event
548+
if (AudioContext) {
549+
const audioCtxErrorHandler = (e) => {
550+
console.error(
551+
"Failed to start the AudioContext. WebAudio playback will not be possible.",
552+
e
553+
);
554+
};
555+
556+
// hack for iOS Audio element controls support
557+
// iOS will only enable AudioContext.resume() when called directly from a UI event
558+
// https://stackoverflow.com/questions/57510426
559+
const events = ["touchstart", "touchend", "mousedown", "keydown"];
560+
561+
const unlock = () => {
562+
events.forEach((e) => document.removeEventListener(e, unlock));
563+
564+
audioCtx = new AudioContext({
565+
latencyHint: "interactive",
566+
});
567+
568+
audioCtx
569+
.resume()
570+
.then(() => {
571+
// hack for iOS to continue playing while locked
572+
audioCtx.onstatechange = () => {
573+
if (audioCtx.state !== "running")
574+
audioCtx.resume().catch(audioCtxErrorHandler);
575+
};
576+
})
577+
.catch(audioCtxErrorHandler);
578+
579+
audioCtx.destination.channelCount =
580+
audioCtx.destination.maxChannelCount;
581+
};
582+
583+
events.forEach((e) => document.addEventListener(e, unlock));
584+
}
511585

512586
const PROGRESS_FACTOR = 100;
513587

@@ -1243,6 +1317,18 @@ <h4>Sync Multiple Clips</h4>
12431317
// correlation options
12441318
const correlationMethodEl = document.getElementById("correlation-method");
12451319

1320+
const correlationMethodRowEl = document.getElementById(
1321+
"correlation-method-row"
1322+
);
1323+
1324+
const correlationThreadsRowEl = document.getElementById(
1325+
"correlation-threads-row"
1326+
);
1327+
1328+
const correlationThresholdRowEl = document.getElementById(
1329+
"correlation-threshold-row"
1330+
);
1331+
12461332
// correlation results
12471333
const correlationCoefficientRowEl = document.getElementById(
12481334
"correlation-coefficient-row"
@@ -1304,14 +1390,34 @@ <h4>Sync Multiple Clips</h4>
13041390
const twoClipsMode = "two-clips-mode";
13051391
const multipleClipsMode = "multiple-clips-mode";
13061392

1307-
let mode = twoClipsMode;
1393+
let mode;
1394+
1395+
// thread option control
1396+
document.getElementById("correlation-threads").value =
1397+
navigator.hardwareConcurrency - 1;
1398+
1399+
const correlationThreadControl = () => {
1400+
if (
1401+
correlationMethodEl.value === "syncWorkerConcurrent" ||
1402+
mode === multipleClipsMode
1403+
) {
1404+
correlationThreadsRowEl.style = "";
1405+
} else {
1406+
correlationThreadsRowEl.style = "display: none;";
1407+
}
1408+
};
1409+
1410+
correlationMethodEl.addEventListener("change", correlationThreadControl);
1411+
13081412
const setTwoClipsMode = () => {
13091413
mode = twoClipsMode;
13101414

13111415
// destroy the multiple clip ffts
13121416
multipleClipsFieldsetEl.style = "display: none;";
13131417
correlationResultsJsonRowEl.style = "display: none;";
1314-
correlationMethodEl.disabled = false;
1418+
correlationThresholdRowEl.style = "display: none;";
1419+
correlationMethodRowEl.style = "";
1420+
correlationThreadControl();
13151421
reset();
13161422
twoClipsModeEl.checked = true;
13171423
multipleClipsModeEl.checked = false;
@@ -1352,10 +1458,12 @@ <h4>Sync Multiple Clips</h4>
13521458

13531459
// destroy the two clip ffts
13541460
twoClipsFieldsetEl.style = "display: none;";
1355-
correlationMethodEl.disabled = true;
13561461
// hide single match results
13571462
correlationCoefficientRowEl.style = "display: none;";
13581463
correlationSampleOffsetRowEl.style = "display: none;";
1464+
correlationThresholdRowEl.style = "";
1465+
correlationMethodRowEl.style = "display: none;";
1466+
correlationThreadControl();
13591467
reset();
13601468
multipleClipsModeEl.checked = true;
13611469
twoClipsModeEl.checked = false;
@@ -1392,10 +1500,12 @@ <h4>Sync Multiple Clips</h4>
13921500
};
13931501

13941502
const destroyCorrelationFFT = () => {
1395-
if (audioData[correlationAudioClipKey])
1396-
audioData[correlationAudioClipKey].fft.destroy();
1397-
1398-
delete audioData[correlationAudioClipKey];
1503+
for (const key in audioData) {
1504+
if (key.match(/correlation/)) {
1505+
audioData[key].fft.destroy();
1506+
delete audioData[key];
1507+
}
1508+
}
13991509

14001510
if (mode === twoClipsMode) {
14011511
resultContainer.innerHTML = getFFTContainer(
@@ -1569,13 +1679,15 @@ <h4>Sync Multiple Clips</h4>
15691679
initialGranularity: parseInt(
15701680
document.getElementById("initial-granularity").value
15711681
),
1572-
correlationThreshold: 0.5,
1682+
correlationThreshold: parseFloat(
1683+
document.getElementById("correlation-threshold")
1684+
),
15731685
});
15741686

15751687
const start = performance.now();
15761688
const results = await synAudio.syncMultiple(
15771689
params,
1578-
window.hardwareConcurrency
1690+
parseInt(document.getElementById("correlation-threads").value)
15791691
);
15801692
const duration = (performance.now() - start) / 1000;
15811693

@@ -1663,7 +1775,7 @@ <h4>Sync Multiple Clips</h4>
16631775
const result = await synAudio[method](
16641776
audioBufferToSynAudioParameter(baseAudioBuffer),
16651777
audioBufferToSynAudioParameter(comparisonAudioBuffer),
1666-
navigator.hardwareConcurrency - 1
1778+
parseInt(document.getElementById("correlation-threads").value)
16671779
);
16681780
const duration = (performance.now() - start) / 1000;
16691781

demo/synaudio.js

+30-9
Original file line numberDiff line numberDiff line change
@@ -438,18 +438,33 @@
438438
// overlap at the start of the buffer by correlation sample size
439439
// overlap at the end of the buffer by correlation sample size
440440

441-
// correlation sample size overlap imposes a maximum thread count for small datasets
442-
const minProcessingRatio = 4 / 1; // unique date / overlap
441+
// initial granularity low -> high, more -> less threads
442+
// correlation sample low -> high, less -> more threads
443+
// file size low -> high, less -> more threads
444+
443445
const correlationSampleSize = this._getCorrelationSampleSize(a, b);
444-
const maxThreads = Math.ceil(
445-
a.samplesDecoded / correlationSampleSize / minProcessingRatio
446+
447+
// rough estimate for a good max thread count for performance
448+
const maxThreads =
449+
(Math.log(a.samplesDecoded * correlationSampleSize) /
450+
Math.log(this._initialGranularity + 1)) *
451+
Math.log(correlationSampleSize / 10000 + 1);
452+
453+
threads = Math.max(
454+
Math.round(
455+
Math.min(
456+
threads,
457+
maxThreads,
458+
a.samplesDecoded / correlationSampleSize / 4
459+
)
460+
),
461+
1
446462
);
447-
threads = Math.min(threads, maxThreads);
448463

449464
const aLength = Math.ceil(a.samplesDecoded / threads);
450465

451466
let offset = 0;
452-
for (let i = 1; i <= threads; i++) {
467+
for (let t = 0; t < threads; t++) {
453468
const aSplit = {
454469
channelData: [],
455470
};
@@ -520,8 +535,12 @@
520535
);
521536
}
522537

523-
async syncWorkerConcurrent(a, b, threads = 1) {
524-
return this._instance._syncWorkerConcurrentMain(a, b, threads);
538+
async syncWorkerConcurrent(a, b, threads) {
539+
return this._instance._syncWorkerConcurrentMain(
540+
a,
541+
b,
542+
threads >= 1 ? threads : 1
543+
);
525544
}
526545

527546
async syncWorker(a, b) {
@@ -532,7 +551,9 @@
532551
return this._instance._sync(a, b);
533552
}
534553

535-
async syncMultiple(clips, threads = 8) {
554+
async syncMultiple(clips, threads) {
555+
threads = threads >= 1 ? threads : 8;
556+
536557
const workers = [];
537558
const graph = [];
538559

0 commit comments

Comments
 (0)