@@ -406,6 +406,7 @@ <h4>Sync Multiple Clips</h4>
406
406
* syncWorker: Executes in a Web Worker.
407
407
* syncWorkerConcurrent: Executes in multiple Web Workers (multi-threaded).
408
408
"
409
+ id ="correlation-method-row "
409
410
>
410
411
< td >
411
412
< label for ="correlation-method "> Correlation Method</ label >
@@ -420,6 +421,23 @@ <h4>Sync Multiple Clips</h4>
420
421
</ select >
421
422
</ td >
422
423
</ 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 >
423
441
< tr
424
442
title ="Selects the sample size in audio samples to compare when determining the best correlation.
425
443
* This value can be increased for better accuracy, at the expense of execution time. "
@@ -454,6 +472,26 @@ <h4>Sync Multiple Clips</h4>
454
472
/>
455
473
</ td >
456
474
</ 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 >
457
495
</ table >
458
496
</ fieldset >
459
497
< fieldset id ="fft-options " class ="column " style ="flex-grow: 1 ">
@@ -503,11 +541,47 @@ <h4>Sync Multiple Clips</h4>
503
541
const comparisonAudioClipKey = "comparison" ;
504
542
const correlationAudioClipKey = "correlation" ;
505
543
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
+ }
511
585
512
586
const PROGRESS_FACTOR = 100 ;
513
587
@@ -1243,6 +1317,18 @@ <h4>Sync Multiple Clips</h4>
1243
1317
// correlation options
1244
1318
const correlationMethodEl = document . getElementById ( "correlation-method" ) ;
1245
1319
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
+
1246
1332
// correlation results
1247
1333
const correlationCoefficientRowEl = document . getElementById (
1248
1334
"correlation-coefficient-row"
@@ -1304,14 +1390,34 @@ <h4>Sync Multiple Clips</h4>
1304
1390
const twoClipsMode = "two-clips-mode" ;
1305
1391
const multipleClipsMode = "multiple-clips-mode" ;
1306
1392
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
+
1308
1412
const setTwoClipsMode = ( ) => {
1309
1413
mode = twoClipsMode ;
1310
1414
1311
1415
// destroy the multiple clip ffts
1312
1416
multipleClipsFieldsetEl . style = "display: none;" ;
1313
1417
correlationResultsJsonRowEl . style = "display: none;" ;
1314
- correlationMethodEl . disabled = false ;
1418
+ correlationThresholdRowEl . style = "display: none;" ;
1419
+ correlationMethodRowEl . style = "" ;
1420
+ correlationThreadControl ( ) ;
1315
1421
reset ( ) ;
1316
1422
twoClipsModeEl . checked = true ;
1317
1423
multipleClipsModeEl . checked = false ;
@@ -1352,10 +1458,12 @@ <h4>Sync Multiple Clips</h4>
1352
1458
1353
1459
// destroy the two clip ffts
1354
1460
twoClipsFieldsetEl . style = "display: none;" ;
1355
- correlationMethodEl . disabled = true ;
1356
1461
// hide single match results
1357
1462
correlationCoefficientRowEl . style = "display: none;" ;
1358
1463
correlationSampleOffsetRowEl . style = "display: none;" ;
1464
+ correlationThresholdRowEl . style = "" ;
1465
+ correlationMethodRowEl . style = "display: none;" ;
1466
+ correlationThreadControl ( ) ;
1359
1467
reset ( ) ;
1360
1468
multipleClipsModeEl . checked = true ;
1361
1469
twoClipsModeEl . checked = false ;
@@ -1392,10 +1500,12 @@ <h4>Sync Multiple Clips</h4>
1392
1500
} ;
1393
1501
1394
1502
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 ( / c o r r e l a t i o n / ) ) {
1505
+ audioData [ key ] . fft . destroy ( ) ;
1506
+ delete audioData [ key ] ;
1507
+ }
1508
+ }
1399
1509
1400
1510
if ( mode === twoClipsMode ) {
1401
1511
resultContainer . innerHTML = getFFTContainer (
@@ -1569,13 +1679,15 @@ <h4>Sync Multiple Clips</h4>
1569
1679
initialGranularity : parseInt (
1570
1680
document . getElementById ( "initial-granularity" ) . value
1571
1681
) ,
1572
- correlationThreshold : 0.5 ,
1682
+ correlationThreshold : parseFloat (
1683
+ document . getElementById ( "correlation-threshold" )
1684
+ ) ,
1573
1685
} ) ;
1574
1686
1575
1687
const start = performance . now ( ) ;
1576
1688
const results = await synAudio . syncMultiple (
1577
1689
params ,
1578
- window . hardwareConcurrency
1690
+ parseInt ( document . getElementById ( "correlation-threads" ) . value )
1579
1691
) ;
1580
1692
const duration = ( performance . now ( ) - start ) / 1000 ;
1581
1693
@@ -1663,7 +1775,7 @@ <h4>Sync Multiple Clips</h4>
1663
1775
const result = await synAudio [ method ] (
1664
1776
audioBufferToSynAudioParameter ( baseAudioBuffer ) ,
1665
1777
audioBufferToSynAudioParameter ( comparisonAudioBuffer ) ,
1666
- navigator . hardwareConcurrency - 1
1778
+ parseInt ( document . getElementById ( "correlation-threads" ) . value )
1667
1779
) ;
1668
1780
const duration = ( performance . now ( ) - start ) / 1000 ;
1669
1781
0 commit comments