Skip to content

Commit ed7292b

Browse files
authored
Add glide parameter in SineGenerator (#12)
1 parent 32a6aa5 commit ed7292b

File tree

1 file changed

+28
-5
lines changed

1 file changed

+28
-5
lines changed

src/arduino/app_utils/audio.py

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)