@@ -23,6 +23,9 @@ class VoiceProcessor extends AudioWorkletProcessor {
2323 case 'RELEASE_PAD' :
2424 this . releaseVoice ( data . padId , data . startTime ) ;
2525 break ;
26+ case 'STOP_PAD' :
27+ this . stopPadVoices ( data . padId , data . startTime ) ;
28+ break ;
2629 case 'UPDATE_PAD_START_END' :
2730 this . updateVoiceBoundaries ( data ) ;
2831 break ;
@@ -107,16 +110,13 @@ class VoiceProcessor extends AudioWorkletProcessor {
107110 // For GATE and LOOP modes, we enforce monophonic behavior per pad (Choke Group).
108111 // If a pad is re-triggered, the existing voice must stop with a very fast fade (5ms)
109112 // to avoid overlaps and digital clicks.
110- if ( triggerMode === 'GATE' || triggerMode === 'LOOP' ) {
111- for ( const voice of this . voices ) {
112- if ( voice . padId === padId && ! voice . finished ) {
113- voice . envelope . phase = 'release' ;
114- voice . envelope . releaseT = 0 ;
115- voice . envelope . release = 0.005 ; // 5ms forced release
116- }
113+ // Every pad is monophonic (choke previous voice of same pad)
114+ for ( const voice of this . voices ) {
115+ if ( voice . padId === padId && ! voice . finished ) {
116+ voice . envelope . phase = 'release' ;
117+ voice . envelope . releaseT = 0 ;
118+ voice . envelope . release = 0.005 ; // 5ms forced release to avoid clicks
117119 }
118- } else {
119- this . releaseVoice ( padId ) ;
120120 }
121121
122122 if ( this . voices . length >= 32 ) {
@@ -175,13 +175,30 @@ class VoiceProcessor extends AudioWorkletProcessor {
175175
176176 for ( const voice of this . voices ) {
177177 if ( voice . padId === padId && ! voice . finished && voice . envelope . phase !== 'release' ) {
178+ // Standard release (e.g. key up) only affects GATE and LOOP
178179 if ( voice . triggerMode === 'GATE' || voice . triggerMode === 'LOOP' ) {
179180 voice . releaseAtFrame = releaseAtFrame ;
180181 }
181182 }
182183 }
183184 }
184185
186+ stopPadVoices ( padId , startTime ) {
187+ const sr = sampleRate || 44100 ;
188+ const releaseAtFrame = startTime !== undefined ? Math . floor ( startTime * sr ) : currentFrame ;
189+
190+ for ( const voice of this . voices ) {
191+ if ( voice . padId === padId && ! voice . finished ) {
192+ // Forced release (STOP) affects all modes
193+ voice . envelope . phase = 'release' ;
194+ voice . envelope . releaseT = 0 ;
195+ voice . envelope . release = 0.005 ; // Fast 5ms fade
196+ voice . envelope . levelAtRelease = voice . envelope . levelAtRelease || 0.5 ;
197+ voice . releaseAtFrame = undefined ; // Process immediately
198+ }
199+ }
200+ }
201+
185202 process ( inputs , outputs , parameters ) {
186203 const output = outputs [ 0 ] ;
187204 const channelL = output [ 0 ] ;
0 commit comments