@@ -423,68 +423,38 @@ class SampleNode {
423
423
this . streamObjectId = params . streamObjectId ;
424
424
/** @type {number } */
425
425
this . offset = options . offset ?? 0 ;
426
- /** @type {LoopMode } */
426
+ /** @type {number } */
427
427
this . startTime = options . startTime ?? 0 ;
428
+ /** @type {boolean } */
429
+ this . isPaused = false ;
428
430
/** @type {number } */
429
431
this . pauseTime = 0 ;
430
432
/** @type {number } */
431
433
this . _playbackRate = 44100 ;
432
434
/** @type {LoopMode } */
433
- this . _loopMode = 'disabled' ;
435
+ this . loopMode = 'disabled' ;
434
436
/** @type {number } */
435
437
this . _pitchScale = 1 ;
438
+ /** @type {number } */
439
+ this . _sourceStartTime = 0 ;
436
440
/** @type {Map<Bus, SampleNodeBus> } */
437
441
this . _sampleNodeBuses = new Map ( ) ;
438
- /** @type {AudioBufferSourceNode } */
442
+ /** @type {AudioBufferSourceNode | null } */
439
443
this . _source = GodotAudio . ctx . createBufferSource ( ) ;
444
+ /** @type {AudioBufferSourceNode["onended"] } */
445
+ this . _onended = null ;
440
446
441
447
this . setPlaybackRate ( options . playbackRate ?? 44100 ) ;
442
- this . setLoopMode ( options . loopMode ?? this . getSample ( ) . loopMode ?? 'disabled' ) ;
448
+ this . loopMode = options . loopMode ?? this . getSample ( ) . loopMode ?? 'disabled' ;
443
449
this . _source . buffer = this . getSample ( ) . getAudioBuffer ( ) ;
444
450
445
- /** @type {SampleNode } */
446
- // eslint-disable-next-line consistent-this
447
- const self = this ;
448
- this . _source . addEventListener ( 'ended' , ( _ ) => {
449
- switch ( self . getSample ( ) . loopMode ) {
450
- case 'disabled' :
451
- GodotAudio . SampleNode . stopSampleNode ( self . id ) ;
452
- break ;
453
- default :
454
- // do nothing
455
- }
456
- } ) ;
451
+ this . _addEndedListener ( ) ;
457
452
458
453
const bus = GodotAudio . Bus . getBus ( params . busIndex ) ;
459
454
const sampleNodeBus = this . getSampleNodeBus ( bus ) ;
460
455
sampleNodeBus . setVolume ( options . volume ) ;
461
456
}
462
457
463
- /**
464
- * Gets the loop mode of the current instance.
465
- * @returns {LoopMode }
466
- */
467
- getLoopMode ( ) {
468
- return this . _loopMode ;
469
- }
470
-
471
- /**
472
- * Sets the loop mode of the current instance.
473
- * @param {LoopMode } val Value to set.
474
- * @returns {void }
475
- */
476
- setLoopMode ( val ) {
477
- this . _loopMode = val ;
478
- switch ( val ) {
479
- case 'forward' :
480
- case 'backward' :
481
- this . _source . loop = true ;
482
- break ;
483
- default :
484
- this . _source . loop = false ;
485
- }
486
- }
487
-
488
458
/**
489
459
* Gets the playback rate.
490
460
* @returns {number }
@@ -542,40 +512,40 @@ class SampleNode {
542
512
* @returns {void }
543
513
*/
544
514
start ( ) {
545
- this . _source . start ( this . offset ) ;
515
+ this . _resetSourceStartTime ( ) ;
516
+ this . _source . start ( this . startTime , this . offset ) ;
546
517
}
547
518
548
519
/**
549
520
* Stops the `SampleNode`.
550
521
* @returns {void }
551
522
*/
552
523
stop ( ) {
553
- this . _source . stop ( ) ;
554
524
this . clear ( ) ;
555
525
}
556
526
527
+ /**
528
+ * Restarts the `SampleNode`.
529
+ */
530
+ restart ( ) {
531
+ this . isPaused = false ;
532
+ this . pauseTime = 0 ;
533
+ this . _resetSourceStartTime ( ) ;
534
+ this . _restart ( ) ;
535
+ }
536
+
557
537
/**
558
538
* Pauses the `SampleNode`.
559
539
* @param {boolean } [enable=true] State of the pause.
560
540
* @returns {void }
561
541
*/
562
542
pause ( enable = true ) {
563
543
if ( enable ) {
564
- this . pauseTime = ( GodotAudio . ctx . currentTime - this . startTime ) / this . playbackRate ;
565
- this . _source . stop ( ) ;
566
- return ;
567
- }
568
-
569
- if ( this . pauseTime === 0 ) {
544
+ this . _pause ( ) ;
570
545
return ;
571
546
}
572
547
573
- this . _source . disconnect ( ) ;
574
- this . _source = GodotAudio . ctx . createBufferSource ( ) ;
575
-
576
- this . _source . buffer = this . getSample ( ) . getAudioBuffer ( ) ;
577
- this . _source . connect ( this . _gain ) ;
578
- this . _source . start ( this . offset + this . pauseTime ) ;
548
+ this . _unpause ( ) ;
579
549
}
580
550
581
551
/**
@@ -623,26 +593,114 @@ class SampleNode {
623
593
* @returns {void }
624
594
*/
625
595
clear ( ) {
626
- this . _source . stop ( ) ;
627
- this . _source . disconnect ( ) ;
628
- this . _source = null ;
596
+ this . isPaused = false ;
597
+ this . pauseTime = 0 ;
598
+
599
+ if ( this . _source != null ) {
600
+ this . _source . removeEventListener ( 'ended' , this . _onended ) ;
601
+ this . _onended = null ;
602
+ this . _source . stop ( ) ;
603
+ this . _source . disconnect ( ) ;
604
+ this . _source = null ;
605
+ }
629
606
630
607
for ( const sampleNodeBus of this . _sampleNodeBuses . values ( ) ) {
631
608
sampleNodeBus . clear ( ) ;
632
609
}
633
610
this . _sampleNodeBuses . clear ( ) ;
634
- this . _sampleNodeBuses = null ;
635
611
636
612
GodotAudio . SampleNode . delete ( this . id ) ;
637
613
}
638
614
615
+ /**
616
+ * Resets the source start time
617
+ * @returns {void }
618
+ */
619
+ _resetSourceStartTime ( ) {
620
+ this . _sourceStartTime = GodotAudio . ctx . currentTime ;
621
+ }
622
+
639
623
/**
640
624
* Syncs the `AudioNode` playback rate based on the `SampleNode` playback rate and pitch scale.
641
625
* @returns {void }
642
626
*/
643
627
_syncPlaybackRate ( ) {
644
628
this . _source . playbackRate . value = this . getPlaybackRate ( ) * this . getPitchScale ( ) ;
645
629
}
630
+
631
+ /**
632
+ * Restarts the `SampleNode`.
633
+ * Honors `isPaused` and `pauseTime`.
634
+ * @returns {void }
635
+ */
636
+ _restart ( ) {
637
+ this . _source . disconnect ( ) ;
638
+ this . _source = GodotAudio . ctx . createBufferSource ( ) ;
639
+ this . _source . buffer = this . getSample ( ) . getAudioBuffer ( ) ;
640
+
641
+ // Make sure that we connect the new source to the sample node bus.
642
+ for ( const sampleNodeBus of this . _sampleNodeBuses . values ( ) ) {
643
+ this . connect ( sampleNodeBus . getInputNode ( ) ) ;
644
+ }
645
+
646
+ this . _addEndedListener ( ) ;
647
+ const pauseTime = this . isPaused
648
+ ? this . pauseTime
649
+ : 0 ;
650
+ this . _source . start ( this . startTime , this . offset + pauseTime ) ;
651
+ }
652
+
653
+ /**
654
+ * Pauses the `SampleNode`.
655
+ * @returns {void }
656
+ */
657
+ _pause ( ) {
658
+ this . isPaused = true ;
659
+ this . pauseTime = ( GodotAudio . ctx . currentTime - this . _sourceStartTime ) / this . getPlaybackRate ( ) ;
660
+ this . _source . stop ( ) ;
661
+ }
662
+
663
+ /**
664
+ * Unpauses the `SampleNode`.
665
+ * @returns {void }
666
+ */
667
+ _unpause ( ) {
668
+ this . _restart ( ) ;
669
+ this . isPaused = false ;
670
+ this . pauseTime = 0 ;
671
+ }
672
+
673
+ /**
674
+ * Adds an "ended" listener to the source node to repeat it if necessary.
675
+ * @returns {void }
676
+ */
677
+ _addEndedListener ( ) {
678
+ if ( this . _onended != null ) {
679
+ this . _source . removeEventListener ( 'ended' , this . _onended ) ;
680
+ }
681
+
682
+ /** @type {SampleNode } */
683
+ // eslint-disable-next-line consistent-this
684
+ const self = this ;
685
+ this . _onended = ( _ ) => {
686
+ if ( self . isPaused ) {
687
+ return ;
688
+ }
689
+
690
+ switch ( self . getSample ( ) . loopMode ) {
691
+ case 'disabled' :
692
+ self . stop ( ) ;
693
+ break ;
694
+ case 'forward' :
695
+ case 'backward' :
696
+ self . restart ( ) ;
697
+ break ;
698
+ default :
699
+ // do nothing
700
+ }
701
+ } ;
702
+ this . _source . addEventListener ( 'ended' , this . _onended ) ;
703
+ }
646
704
}
647
705
648
706
/**
0 commit comments