wishartgroup commited on
Commit
ffd00cc
Β·
verified Β·
1 Parent(s): 806cefe

Update float_streamer.py

Browse files
Files changed (1) hide show
  1. 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
- After speech ends, gradually increases dampening over several chunks for smooth transition."""
304
 
305
  if self._transition_chunks > 0:
306
- # Gradual transition: interpolate dampening from 1.0 (speech) to target (idle)
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: if set, called after ODE solve but before frame decode.
351
- Used to drain buffer and fire speech_start at the exact right moment.
352
- dampening: multiply motion latent by this factor (idle: reduces motion amplitude)
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
- # Random initial noise for ODE
371
- x0 = torch.randn(1, self.frames_per_chunk, self.opt.dim_w, device=self.device)
 
 
 
 
 
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
- # Decode latent to pixel frames and push to buffer
 
 
 
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
- # If this is the first speech chunk, drain idle frames BEFORE decoding
 
 
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")