@@ -31,6 +31,7 @@ def __init__(self, sample_rate: int):
3131 # envelope parameters (attack/release in seconds)
3232 self .attack = 0.01
3333 self .release = 0.03
34+ self .glide = 0.02
3435
3536 # reusable buffers
3637 self ._buf_N = 0
@@ -88,7 +89,7 @@ def set_state(self, state: dict) -> None:
8889 if "freq_last" in state :
8990 self ._freq_last = float (state ["freq_last" ])
9091
91- def set_envelope_params (self , attack : float , release : float ) -> None :
92+ def set_envelope_params (self , attack : float , release : float , glide : float ) -> None :
9293 """Update attack and release envelope parameters.
9394
9495 Args:
@@ -99,6 +100,7 @@ def set_envelope_params(self, attack: float, release: float) -> None:
99100 """
100101 self .attack = float (max (0.0 , attack ))
101102 self .release = float (max (0.0 , release ))
103+ self .glide = float (max (0.0 , glide ))
102104
103105 def generate_block (self , freq : float , amp_target : float , block_dur : float , master_volume : float ):
104106 """Generate a block of float32 audio samples.
@@ -149,10 +151,29 @@ def generate_block(self, freq: float, amp_target: float, block_dur: float, maste
149151 envelope [:] = np .linspace (amp_current , next_amp , N , dtype = np .float32 )
150152 amp_current = float (envelope [- 1 ])
151153
152- # oscillator
153- phase_incr = 2.0 * math .pi * float (freq ) / float (self .sample_rate )
154+ # frequency glide (portamento)
155+ freq_current = float (self ._freq_last )
156+ freq_target = float (freq )
157+ glide = float (self .glide )
154158 phase_incs = self ._buf_phase_incs [:N ]
155- phase_incs .fill (phase_incr )
159+
160+ if glide > 0.0 and freq_current != freq_target :
161+ # Apply glide smoothing over time
162+ frac = min (1.0 , block_dur / glide )
163+ next_freq = freq_current + (freq_target - freq_current ) * frac
164+
165+ # Linear interpolation within block
166+ freq_ramp = np .linspace (freq_current , next_freq , N , dtype = np .float32 )
167+ phase_incs [:] = 2.0 * math .pi * freq_ramp / float (self .sample_rate )
168+
169+ freq_current = float (next_freq )
170+ else :
171+ # No glide or already at target
172+ phase_incr = 2.0 * math .pi * freq_target / float (self .sample_rate )
173+ phase_incs .fill (phase_incr )
174+ freq_current = freq_target
175+
176+ # oscillator (phase accumulation)
156177 np .cumsum (phase_incs , dtype = np .float32 , out = phases )
157178 phases += self ._phase
158179 self ._phase = float (phases [- 1 ] % (2.0 * math .pi ))
@@ -168,6 +189,6 @@ def generate_block(self, freq: float, amp_target: float, block_dur: float, maste
168189
169190 # update state
170191 self ._amp_current = amp_current
171- self ._freq_last = float ( freq )
192+ self ._freq_last = freq_current
172193
173194 return samples
0 commit comments