Spaces:
Paused
Paused
Update float_streamer.py
Browse files- float_streamer.py +24 -23
float_streamer.py
CHANGED
|
@@ -300,15 +300,12 @@ class FloatStreamer:
|
|
| 300 |
|
| 301 |
def _generate_idle_chunk(self):
|
| 302 |
"""Generate one chunk of idle motion using proper silence features + dampening.
|
| 303 |
-
|
| 304 |
|
| 305 |
if self._transition_chunks > 0:
|
| 306 |
-
|
| 307 |
-
# 3β1.0, 2β0.78, 1β0.55 (or similar smooth curve)
|
| 308 |
-
progress = self._transition_chunks / 3.0 # 1.0 β 0.33
|
| 309 |
damp = 1.0 * progress + self._idle_dampening * (1.0 - progress)
|
| 310 |
self._transition_chunks -= 1
|
| 311 |
-
logger.debug(f"[STREAMER] Transition chunk: dampening={damp:.2f}, remaining={self._transition_chunks}")
|
| 312 |
else:
|
| 313 |
damp = self._idle_dampening
|
| 314 |
|
|
@@ -319,6 +316,7 @@ class FloatStreamer:
|
|
| 319 |
a_cfg_scale=1.0,
|
| 320 |
e_cfg_scale=1.0,
|
| 321 |
dampening=damp,
|
|
|
|
| 322 |
)
|
| 323 |
|
| 324 |
def _make_speech_start_callback(self, event):
|
|
@@ -342,14 +340,14 @@ class FloatStreamer:
|
|
| 342 |
return SpeechStartCallback()
|
| 343 |
|
| 344 |
@torch.no_grad()
|
| 345 |
-
def _generate_chunk(self, wa, we, actual_frames=None, a_cfg_scale=None, e_cfg_scale=None, dampening=None, pre_decode_callback=None):
|
| 346 |
"""
|
| 347 |
Generate one 50-frame chunk and push decoded frames to buffer.
|
| 348 |
|
| 349 |
Args:
|
| 350 |
-
pre_decode_callback:
|
| 351 |
-
|
| 352 |
-
|
| 353 |
"""
|
| 354 |
if actual_frames is None:
|
| 355 |
actual_frames = self.frames_per_chunk
|
|
@@ -367,8 +365,13 @@ class FloatStreamer:
|
|
| 367 |
prev_x = self.prev_x
|
| 368 |
prev_wa = self.prev_wa
|
| 369 |
|
| 370 |
-
#
|
| 371 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 372 |
|
| 373 |
# Time steps for ODE solver
|
| 374 |
time_steps = torch.linspace(0, 1, self.opt.nfe, device=self.device)
|
|
@@ -396,27 +399,29 @@ class FloatStreamer:
|
|
| 396 |
method=self.opt.torchdiffeq_ode_method)
|
| 397 |
sample = trajectory[-1] # (1, frames_per_chunk, dim_w)
|
| 398 |
|
| 399 |
-
# Apply dampening to motion latent (idle: reduces all motion including mouth)
|
| 400 |
-
# This matches idle_generator.py's approach of scaling r_d
|
| 401 |
-
if dampening is not None:
|
| 402 |
-
sample = sample * dampening
|
| 403 |
-
|
| 404 |
t_ode = time.time() - t0
|
| 405 |
|
| 406 |
-
# Update state for next chunk
|
|
|
|
| 407 |
with self._lock:
|
| 408 |
self.prev_x = sample[:, -self.num_prev_frames:].clone()
|
| 409 |
self.prev_wa = wa[:, -self.num_prev_frames:].clone()
|
| 410 |
|
| 411 |
-
#
|
|
|
|
|
|
|
|
|
|
| 412 |
# Skip if this is an idle chunk and speech has arrived (discard stale idle)
|
| 413 |
if dampening is not None and self._discard_idle_frames:
|
| 414 |
logger.info("[STREAMER] Discarding idle chunk β speech arriving")
|
| 415 |
return
|
| 416 |
|
| 417 |
-
#
|
|
|
|
|
|
|
| 418 |
if pre_decode_callback:
|
| 419 |
pre_decode_callback.drain()
|
|
|
|
| 420 |
|
| 421 |
t_dec = time.time()
|
| 422 |
for t in range(min(actual_frames, sample.shape[1])):
|
|
@@ -435,10 +440,6 @@ class FloatStreamer:
|
|
| 435 |
except queue.Full:
|
| 436 |
logger.warning("[STREAMER] Frame buffer full, dropping frame")
|
| 437 |
|
| 438 |
-
# Fire speech_start AFTER frames are in buffer β audio and video start together
|
| 439 |
-
if pre_decode_callback:
|
| 440 |
-
pre_decode_callback.fire()
|
| 441 |
-
|
| 442 |
t_dec_done = time.time()
|
| 443 |
logger.debug(f"[STREAMER] Chunk: ODE={t_ode:.2f}s decode={t_dec_done-t_dec:.2f}s "
|
| 444 |
f"frames={actual_frames} total={t_dec_done-t0:.2f}s")
|
|
|
|
| 300 |
|
| 301 |
def _generate_idle_chunk(self):
|
| 302 |
"""Generate one chunk of idle motion using proper silence features + dampening.
|
| 303 |
+
Uses fixed seed for consistent x0 noise β prevents flicker at chunk boundaries."""
|
| 304 |
|
| 305 |
if self._transition_chunks > 0:
|
| 306 |
+
progress = self._transition_chunks / 3.0
|
|
|
|
|
|
|
| 307 |
damp = 1.0 * progress + self._idle_dampening * (1.0 - progress)
|
| 308 |
self._transition_chunks -= 1
|
|
|
|
| 309 |
else:
|
| 310 |
damp = self._idle_dampening
|
| 311 |
|
|
|
|
| 316 |
a_cfg_scale=1.0,
|
| 317 |
e_cfg_scale=1.0,
|
| 318 |
dampening=damp,
|
| 319 |
+
fixed_seed=42,
|
| 320 |
)
|
| 321 |
|
| 322 |
def _make_speech_start_callback(self, event):
|
|
|
|
| 340 |
return SpeechStartCallback()
|
| 341 |
|
| 342 |
@torch.no_grad()
|
| 343 |
+
def _generate_chunk(self, wa, we, actual_frames=None, a_cfg_scale=None, e_cfg_scale=None, dampening=None, pre_decode_callback=None, fixed_seed=None):
|
| 344 |
"""
|
| 345 |
Generate one 50-frame chunk and push decoded frames to buffer.
|
| 346 |
|
| 347 |
Args:
|
| 348 |
+
pre_decode_callback: object with drain()/fire() for speech sync.
|
| 349 |
+
dampening: multiply motion latent by this factor (display-only, not saved to prev_x).
|
| 350 |
+
fixed_seed: if set, use deterministic x0 noise for chunk consistency.
|
| 351 |
"""
|
| 352 |
if actual_frames is None:
|
| 353 |
actual_frames = self.frames_per_chunk
|
|
|
|
| 365 |
prev_x = self.prev_x
|
| 366 |
prev_wa = self.prev_wa
|
| 367 |
|
| 368 |
+
# Initial noise for ODE β fixed seed for idle consistency
|
| 369 |
+
if fixed_seed is not None:
|
| 370 |
+
g = torch.Generator(device=self.device)
|
| 371 |
+
g.manual_seed(fixed_seed)
|
| 372 |
+
x0 = torch.randn(1, self.frames_per_chunk, self.opt.dim_w, device=self.device, generator=g)
|
| 373 |
+
else:
|
| 374 |
+
x0 = torch.randn(1, self.frames_per_chunk, self.opt.dim_w, device=self.device)
|
| 375 |
|
| 376 |
# Time steps for ODE solver
|
| 377 |
time_steps = torch.linspace(0, 1, self.opt.nfe, device=self.device)
|
|
|
|
| 399 |
method=self.opt.torchdiffeq_ode_method)
|
| 400 |
sample = trajectory[-1] # (1, frames_per_chunk, dim_w)
|
| 401 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 402 |
t_ode = time.time() - t0
|
| 403 |
|
| 404 |
+
# Update state for next chunk BEFORE dampening
|
| 405 |
+
# The model expects undampened prev_x context β dampening is display-only
|
| 406 |
with self._lock:
|
| 407 |
self.prev_x = sample[:, -self.num_prev_frames:].clone()
|
| 408 |
self.prev_wa = wa[:, -self.num_prev_frames:].clone()
|
| 409 |
|
| 410 |
+
# Apply dampening AFTER saving state (display-only, doesn't affect next chunk)
|
| 411 |
+
if dampening is not None:
|
| 412 |
+
sample = sample * dampening
|
| 413 |
+
|
| 414 |
# Skip if this is an idle chunk and speech has arrived (discard stale idle)
|
| 415 |
if dampening is not None and self._discard_idle_frames:
|
| 416 |
logger.info("[STREAMER] Discarding idle chunk β speech arriving")
|
| 417 |
return
|
| 418 |
|
| 419 |
+
# For first speech chunk: drain idle frames then fire speech_start
|
| 420 |
+
# Both BEFORE decode β so client gets event before any speech frames
|
| 421 |
+
# WS loop checks events first, then frames, so audio starts with first frame
|
| 422 |
if pre_decode_callback:
|
| 423 |
pre_decode_callback.drain()
|
| 424 |
+
pre_decode_callback.fire()
|
| 425 |
|
| 426 |
t_dec = time.time()
|
| 427 |
for t in range(min(actual_frames, sample.shape[1])):
|
|
|
|
| 440 |
except queue.Full:
|
| 441 |
logger.warning("[STREAMER] Frame buffer full, dropping frame")
|
| 442 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 443 |
t_dec_done = time.time()
|
| 444 |
logger.debug(f"[STREAMER] Chunk: ODE={t_ode:.2f}s decode={t_dec_done-t_dec:.2f}s "
|
| 445 |
f"frames={actual_frames} total={t_dec_done-t0:.2f}s")
|