@@ -7,6 +7,22 @@ let gainCurveFunc = (val) => Math.pow(val, 2);
77function applyGainCurve ( val ) {
88 return gainCurveFunc ( val ) ;
99}
10+
11+ /**
12+ * Equal Power Crossfade function.
13+ * Smoothly transitions between signals A and B, maintaining consistent perceived loudness.
14+ *
15+ * @param {number } a - Signal A (can be a single value or an array value in buffer processing).
16+ * @param {number } b - Signal B (can be a single value or an array value in buffer processing).
17+ * @param {number } m - Crossfade parameter (0.0 = all A, 1.0 = all B, 0.5 = equal mix).
18+ * @returns {number } Crossfaded output value.
19+ */
20+ function crossfade ( a , b , m ) {
21+ const aGain = Math . sin ( ( 1 - m ) * 0.5 * Math . PI ) ;
22+ const bGain = Math . sin ( m * 0.5 * Math . PI ) ;
23+ return a * aGain + b * bGain ;
24+ }
25+
1026// function setGainCurve(newGainCurveFunc) {
1127// gainCurveFunc = newGainCurveFunc;
1228// }
@@ -349,7 +365,8 @@ export class ADSR {
349365.add(x=>x.delay(.1).mul(.8))
350366.out()*/
351367const MAX_DELAY_TIME = 10 ;
352- export class Delay {
368+ export class PitchDelay {
369+ lpf = new TwoPoleFilter ( ) ;
353370 constructor ( _props = { } ) {
354371 this . buffer = new Float32Array ( MAX_DELAY_TIME * SAMPLE_RATE ) ;
355372 this . writeIdx = 0 ;
@@ -374,7 +391,38 @@ export class Delay {
374391 } else {
375392 index = Math . floor ( this . readIdx * speed ) % this . numSamples ;
376393 }
377- return this . buffer [ index ] ;
394+ const s = this . lpf . update ( this . buffer [ index ] , 0.9 , 0 ) ;
395+
396+ return s ;
397+ }
398+ }
399+
400+ export class Delay {
401+ writeIdx = 0 ;
402+ readIdx = 0 ;
403+ buffer = new Float32Array ( MAX_DELAY_TIME * SAMPLE_RATE ) ; //.fill(0)
404+ write ( s , delayTime ) {
405+ this . writeIdx = ( this . writeIdx + 1 ) % this . buffer . length ;
406+ this . buffer [ this . writeIdx ] = s ;
407+ // Calculate how far in the past to read
408+ let numSamples = Math . min ( Math . floor ( SAMPLE_RATE * delayTime ) , this . buffer . length - 1 ) ;
409+ this . readIdx = this . writeIdx - numSamples ;
410+ // If past the start of the buffer, wrap around
411+ if ( this . readIdx < 0 ) this . readIdx += this . buffer . length ;
412+ }
413+ update ( input , delayTime ) {
414+ this . write ( input , delayTime ) ;
415+ return this . buffer [ this . readIdx ] ;
416+ }
417+ }
418+ //TODO: Figure out why clicking at the start off the buffer
419+ export class Chorus {
420+ delay = new Delay ( ) ;
421+ modulator = new TriOsc ( ) ;
422+ update ( input , mix , delayTime , modulationFreq , modulationDepth ) {
423+ const m = this . modulator . update ( modulationFreq ) * modulationDepth ;
424+ const c = this . delay . update ( input , delayTime * ( 1 + m ) ) ;
425+ return crossfade ( input , c , mix ) ;
378426 }
379427}
380428
@@ -540,6 +588,7 @@ let shapes = {
540588} ;
541589
542590const defaultDefaultValues = {
591+ chorus : 0 ,
543592 note : 48 ,
544593 s : 'triangle' ,
545594 gain : 1 ,
@@ -616,6 +665,7 @@ export class DoughVoice {
616665 $ . shapevol = applyGainCurve ( $ . shapevol ?? getDefaultValue ( 'shapevol' ) ) ;
617666 $ . distortvol = applyGainCurve ( $ . distortvol ?? getDefaultValue ( 'distortvol' ) ) ;
618667 $ . i = $ . i ?? getDefaultValue ( 'i' ) ;
668+ $ . chorus = $ . chorus ?? getDefaultValue ( 'chorus' ) ;
619669 $ . fft = $ . fft ?? getDefaultValue ( 'fft' ) ;
620670 $ . pan = $ . pan ?? getDefaultValue ( 'pan' ) ;
621671 $ . orbit = $ . orbit ?? getDefaultValue ( 'orbit' ) ;
@@ -696,6 +746,7 @@ export class DoughVoice {
696746 }
697747
698748 // channelwise effects setup
749+ $ . _chorus = $ . chorus ? [ ] : null ;
699750 $ . _lpf = $ . cutoff ? [ ] : null ;
700751 $ . _hpf = $ . hcutoff ? [ ] : null ;
701752 $ . _bpf = $ . bandf ? [ ] : null ;
@@ -706,6 +757,7 @@ export class DoughVoice {
706757 $ . _lpf ?. push ( new TwoPoleFilter ( ) ) ;
707758 $ . _hpf ?. push ( new TwoPoleFilter ( ) ) ;
708759 $ . _bpf ?. push ( new TwoPoleFilter ( ) ) ;
760+ $ . _chorus ?. push ( new Chorus ( ) ) ;
709761 $ . _coarse ?. push ( new Coarse ( ) ) ;
710762 $ . _crush ?. push ( new Crush ( ) ) ;
711763 $ . _distort ?. push ( new Distort ( ) ) ;
@@ -786,6 +838,10 @@ export class DoughVoice {
786838 this . out [ i ] = this . _buffers [ i ] . update ( freq ) ;
787839 }
788840 this . out [ i ] = this . out [ i ] * this . gain * this . velocity ;
841+ if ( this . _chorus ) {
842+ const c = this . _chorus [ i ] . update ( this . out [ i ] , this . chorus , 0.03 + 0.05 * i , 1 , 0.11 ) ;
843+ this . out [ i ] = c + this . out [ i ] ;
844+ }
789845
790846 if ( this . _lpf ) {
791847 this . _lpf [ i ] . update ( this . out [ i ] , lpf , this . resonance ) ;
@@ -842,8 +898,8 @@ export class Dough {
842898 this . sampleRate = sampleRate ;
843899 this . t = Math . floor ( currentTime * sampleRate ) ; // samples
844900 // console.log('init dough', this.sampleRate, this.t);
845- this . _delayL = new Delay ( ) ;
846- this . _delayR = new Delay ( ) ;
901+ this . _delayL = new PitchDelay ( ) ;
902+ this . _delayR = new PitchDelay ( ) ;
847903 }
848904 loadSample ( name , channels , sampleRate ) {
849905 BufferPlayer . samples . set ( name , { channels, sampleRate } ) ;
0 commit comments