@@ -18,6 +18,7 @@ class SineGenerator:
1818 sample_rate (int): Audio sample rate in Hz.
1919 attack (float): Attack time for amplitude smoothing in seconds.
2020 release (float): Release time for amplitude smoothing in seconds.
21+ glide (float): Glide time for frequency smoothing in seconds.
2122 """
2223
2324 def __init__ (self , sample_rate : int ):
@@ -31,6 +32,7 @@ def __init__(self, sample_rate: int):
3132 # envelope parameters (attack/release in seconds)
3233 self .attack = 0.01
3334 self .release = 0.03
35+ self .glide = 0.02
3436
3537 # reusable buffers
3638 self ._buf_N = 0
@@ -88,17 +90,19 @@ def set_state(self, state: dict) -> None:
8890 if "freq_last" in state :
8991 self ._freq_last = float (state ["freq_last" ])
9092
91- def set_envelope_params (self , attack : float , release : float ) -> None :
93+ def set_envelope_params (self , attack : float , release : float , glide : float ) -> None :
9294 """Update attack and release envelope parameters.
9395
9496 Args:
9597 attack (float): Attack time in seconds (time to rise to target
9698 amplitude when increasing amplitude).
9799 release (float): Release time in seconds (time to fall to target
98100 amplitude when decreasing amplitude).
101+ glide (float): Glide time in seconds (time to reach target frequency).
99102 """
100103 self .attack = float (max (0.0 , attack ))
101104 self .release = float (max (0.0 , release ))
105+ self .glide = float (max (0.0 , glide ))
102106
103107 def generate_block (self , freq : float , amp_target : float , block_dur : float , master_volume : float ):
104108 """Generate a block of float32 audio samples.
@@ -149,10 +153,29 @@ def generate_block(self, freq: float, amp_target: float, block_dur: float, maste
149153 envelope [:] = np .linspace (amp_current , next_amp , N , dtype = np .float32 )
150154 amp_current = float (envelope [- 1 ])
151155
152- # oscillator
153- phase_incr = 2.0 * math .pi * float (freq ) / float (self .sample_rate )
156+ # frequency glide (portamento)
157+ freq_current = float (self ._freq_last )
158+ freq_target = float (freq )
159+ glide = float (self .glide )
154160 phase_incs = self ._buf_phase_incs [:N ]
155- phase_incs .fill (phase_incr )
161+
162+ if glide > 0.0 and freq_current != freq_target :
163+ # Apply glide smoothing over time
164+ frac = min (1.0 , block_dur / glide )
165+ next_freq = freq_current + (freq_target - freq_current ) * frac
166+
167+ # Linear interpolation within block
168+ freq_ramp = np .linspace (freq_current , next_freq , N , dtype = np .float32 )
169+ phase_incs [:] = 2.0 * math .pi * freq_ramp / float (self .sample_rate )
170+
171+ freq_current = float (next_freq )
172+ else :
173+ # No glide or already at target
174+ phase_incr = 2.0 * math .pi * freq_target / float (self .sample_rate )
175+ phase_incs .fill (phase_incr )
176+ freq_current = freq_target
177+
178+ # oscillator (phase accumulation)
156179 np .cumsum (phase_incs , dtype = np .float32 , out = phases )
157180 phases += self ._phase
158181 self ._phase = float (phases [- 1 ] % (2.0 * math .pi ))
@@ -168,6 +191,6 @@ def generate_block(self, freq: float, amp_target: float, block_dur: float, maste
168191
169192 # update state
170193 self ._amp_current = amp_current
171- self ._freq_last = float ( freq )
194+ self ._freq_last = freq_current
172195
173196 return samples
0 commit comments