Commit
Β·
d96e1a0
1
Parent(s):
86dd29c
fixing logging
Browse files- jam_worker.py +44 -26
jam_worker.py
CHANGED
|
@@ -552,26 +552,25 @@ class JamWorker(threading.Thread):
|
|
| 552 |
return self.idx <= (horizon_anchor + self._max_buffer_ahead)
|
| 553 |
|
| 554 |
def _emit_ready(self):
|
| 555 |
-
"""Emit next chunk(s) if the spool has enough samples. With
|
| 556 |
|
| 557 |
|
| 558 |
-
QDB_SILENCE = -55.0
|
| 559 |
EPS = 1e-12
|
| 560 |
|
| 561 |
def rms_dbfs(x: np.ndarray) -> float:
|
| 562 |
-
# x: float32 [-1,1]; return single-channel RMS dBFS (mean over channels if stereo)
|
| 563 |
if x.ndim == 2:
|
| 564 |
x = x.mean(axis=1)
|
| 565 |
rms = float(np.sqrt(np.mean(np.square(x)) + EPS))
|
| 566 |
return 20.0 * np.log10(max(rms, EPS))
|
| 567 |
|
| 568 |
def qbar_rms_dbfs(x: np.ndarray, seg_len: int) -> list[float]:
|
| 569 |
-
vals = []
|
| 570 |
if x.ndim == 2:
|
| 571 |
mono = x.mean(axis=1)
|
| 572 |
else:
|
| 573 |
mono = x
|
| 574 |
N = mono.shape[0]
|
|
|
|
| 575 |
for i in range(0, N, seg_len):
|
| 576 |
seg = mono[i:min(i + seg_len, N)]
|
| 577 |
if seg.size == 0:
|
|
@@ -580,32 +579,51 @@ class JamWorker(threading.Thread):
|
|
| 580 |
vals.append(20.0 * np.log10(max(r, EPS)))
|
| 581 |
return vals
|
| 582 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 583 |
while True:
|
| 584 |
start, end = self._bar_clock.bounds_for_chunk(self.idx, self.params.bars_per_chunk)
|
| 585 |
if end > self._spool_written:
|
| 586 |
-
# Not enough audio buffered for the next full chunk
|
| 587 |
-
# Debug the readiness gap once per idx
|
| 588 |
-
# print(f"[emit idx={self.idx}] need end={end}, have={self._spool_written} (Ξ={end - self._spool_written})")
|
| 589 |
break
|
| 590 |
|
| 591 |
-
|
| 592 |
-
loop = self._spool[start:end] # shape: [samples, channels] @ target_sr
|
| 593 |
|
| 594 |
-
# ----
|
| 595 |
-
spb = self._bar_clock.bar_samps
|
| 596 |
-
qlen = max(1, spb // 4)
|
| 597 |
q_rms_pre = qbar_rms_dbfs(loop, qlen)
|
| 598 |
-
# Mark segments that look like near-silence
|
| 599 |
silent_marks_pre = ["π’" if v > QDB_SILENCE else "π₯" for v in q_rms_pre[:8]]
|
| 600 |
-
print(f"[emit idx={self.idx}] pre-LM qRMS dBFS: {
|
| 601 |
|
| 602 |
-
# Loudness match
|
| 603 |
-
|
| 604 |
if self.params.ref_loop is not None and self.params.loudness_mode != "none":
|
| 605 |
ref = self.params.ref_loop.as_stereo().resample(self.params.target_sr)
|
| 606 |
wav = au.Waveform(loop.copy(), int(self.params.target_sr))
|
| 607 |
try:
|
| 608 |
-
matched,
|
| 609 |
ref, wav,
|
| 610 |
method=self.params.loudness_mode,
|
| 611 |
headroom_db=self.params.headroom_db
|
|
@@ -614,13 +632,15 @@ class JamWorker(threading.Thread):
|
|
| 614 |
except Exception as e:
|
| 615 |
print(f"[emit idx={self.idx}] loudness-match ERROR: {e}; proceeding with un-matched audio")
|
| 616 |
|
| 617 |
-
|
|
|
|
|
|
|
| 618 |
q_rms_post = qbar_rms_dbfs(loop, qlen)
|
| 619 |
silent_marks_post = ["π’" if v > QDB_SILENCE else "π₯" for v in q_rms_post[:8]]
|
| 620 |
-
if
|
| 621 |
-
print(f"[emit idx={self.idx}] post-LM qRMS dBFS: {
|
| 622 |
else:
|
| 623 |
-
print(f"[emit idx={self.idx}] post-LM qRMS dBFS: {
|
| 624 |
|
| 625 |
# Encode & ship
|
| 626 |
audio_b64, total_samples, channels = wav_bytes_base64(loop, int(self.params.target_sr))
|
|
@@ -639,22 +659,19 @@ class JamWorker(threading.Thread):
|
|
| 639 |
}
|
| 640 |
chunk = JamChunk(index=self.idx, audio_base64=audio_b64, metadata=meta)
|
| 641 |
|
| 642 |
-
# Emit to outbox
|
| 643 |
with self._cv:
|
| 644 |
self._outbox[self.idx] = chunk
|
| 645 |
self._cv.notify_all()
|
| 646 |
|
| 647 |
-
# ---- DEBUG: boundary bookkeeping ----
|
| 648 |
print(f"[emit idx={self.idx}] slice [{start}:{end}] (len={end-start}), spool_written={self._spool_written}")
|
| 649 |
-
|
| 650 |
self.idx += 1
|
| 651 |
|
| 652 |
-
#
|
| 653 |
with self._lock:
|
| 654 |
if self._pending_token_splice is not None:
|
| 655 |
spliced = self._coerce_tokens(self._pending_token_splice["tokens"])
|
| 656 |
try:
|
| 657 |
-
self.state.context_tokens = spliced
|
| 658 |
self._pending_token_splice = None
|
| 659 |
print(f"[emit idx={self.idx}] installed token splice (in-place)")
|
| 660 |
except Exception:
|
|
@@ -673,6 +690,7 @@ class JamWorker(threading.Thread):
|
|
| 673 |
self._pending_reseed = None
|
| 674 |
print(f"[emit idx={self.idx}] performed full reseed")
|
| 675 |
|
|
|
|
| 676 |
# ---------- main loop ----------
|
| 677 |
|
| 678 |
def run(self):
|
|
|
|
| 552 |
return self.idx <= (horizon_anchor + self._max_buffer_ahead)
|
| 553 |
|
| 554 |
def _emit_ready(self):
|
| 555 |
+
"""Emit next chunk(s) if the spool has enough samples. With robust RMS debug."""
|
| 556 |
|
| 557 |
|
| 558 |
+
QDB_SILENCE = -55.0
|
| 559 |
EPS = 1e-12
|
| 560 |
|
| 561 |
def rms_dbfs(x: np.ndarray) -> float:
|
|
|
|
| 562 |
if x.ndim == 2:
|
| 563 |
x = x.mean(axis=1)
|
| 564 |
rms = float(np.sqrt(np.mean(np.square(x)) + EPS))
|
| 565 |
return 20.0 * np.log10(max(rms, EPS))
|
| 566 |
|
| 567 |
def qbar_rms_dbfs(x: np.ndarray, seg_len: int) -> list[float]:
|
|
|
|
| 568 |
if x.ndim == 2:
|
| 569 |
mono = x.mean(axis=1)
|
| 570 |
else:
|
| 571 |
mono = x
|
| 572 |
N = mono.shape[0]
|
| 573 |
+
vals = []
|
| 574 |
for i in range(0, N, seg_len):
|
| 575 |
seg = mono[i:min(i + seg_len, N)]
|
| 576 |
if seg.size == 0:
|
|
|
|
| 579 |
vals.append(20.0 * np.log10(max(r, EPS)))
|
| 580 |
return vals
|
| 581 |
|
| 582 |
+
def fmt_db_list(vals):
|
| 583 |
+
return ['%5.1f' % v for v in vals[:8]]
|
| 584 |
+
|
| 585 |
+
def extract_gain_db(g):
|
| 586 |
+
# Accept float/int, dict{'gain_db': ...}, tuple/list, or None
|
| 587 |
+
if g is None:
|
| 588 |
+
return None
|
| 589 |
+
if isinstance(g, (int, float)):
|
| 590 |
+
return float(g)
|
| 591 |
+
if isinstance(g, dict):
|
| 592 |
+
for k in ('gain_db', 'gain', 'applied_gain_db'):
|
| 593 |
+
if k in g:
|
| 594 |
+
try:
|
| 595 |
+
return float(g[k])
|
| 596 |
+
except Exception:
|
| 597 |
+
pass
|
| 598 |
+
return None
|
| 599 |
+
if isinstance(g, (list, tuple)) and g:
|
| 600 |
+
try:
|
| 601 |
+
return float(g[0])
|
| 602 |
+
except Exception:
|
| 603 |
+
return None
|
| 604 |
+
return None
|
| 605 |
+
|
| 606 |
while True:
|
| 607 |
start, end = self._bar_clock.bounds_for_chunk(self.idx, self.params.bars_per_chunk)
|
| 608 |
if end > self._spool_written:
|
|
|
|
|
|
|
|
|
|
| 609 |
break
|
| 610 |
|
| 611 |
+
loop = self._spool[start:end]
|
|
|
|
| 612 |
|
| 613 |
+
# ---- pre-LM diagnostics ----
|
| 614 |
+
spb = self._bar_clock.bar_samps
|
| 615 |
+
qlen = max(1, spb // 4)
|
| 616 |
q_rms_pre = qbar_rms_dbfs(loop, qlen)
|
|
|
|
| 617 |
silent_marks_pre = ["π’" if v > QDB_SILENCE else "π₯" for v in q_rms_pre[:8]]
|
| 618 |
+
print(f"[emit idx={self.idx}] pre-LM qRMS dBFS: {fmt_db_list(q_rms_pre)} {''.join(silent_marks_pre)}")
|
| 619 |
|
| 620 |
+
# Loudness match (optional)
|
| 621 |
+
gain_db_applied_raw = None
|
| 622 |
if self.params.ref_loop is not None and self.params.loudness_mode != "none":
|
| 623 |
ref = self.params.ref_loop.as_stereo().resample(self.params.target_sr)
|
| 624 |
wav = au.Waveform(loop.copy(), int(self.params.target_sr))
|
| 625 |
try:
|
| 626 |
+
matched, gain_db_applied_raw = match_loudness_to_reference(
|
| 627 |
ref, wav,
|
| 628 |
method=self.params.loudness_mode,
|
| 629 |
headroom_db=self.params.headroom_db
|
|
|
|
| 632 |
except Exception as e:
|
| 633 |
print(f"[emit idx={self.idx}] loudness-match ERROR: {e}; proceeding with un-matched audio")
|
| 634 |
|
| 635 |
+
gain_db = extract_gain_db(gain_db_applied_raw)
|
| 636 |
+
|
| 637 |
+
# ---- post-LM diagnostics ----
|
| 638 |
q_rms_post = qbar_rms_dbfs(loop, qlen)
|
| 639 |
silent_marks_post = ["π’" if v > QDB_SILENCE else "π₯" for v in q_rms_post[:8]]
|
| 640 |
+
if gain_db is None:
|
| 641 |
+
print(f"[emit idx={self.idx}] post-LM qRMS dBFS: {fmt_db_list(q_rms_post)} {''.join(silent_marks_post)} (LM: none)")
|
| 642 |
else:
|
| 643 |
+
print(f"[emit idx={self.idx}] post-LM qRMS dBFS: {fmt_db_list(q_rms_post)} {''.join(silent_marks_post)} (LM gain {gain_db:+.2f} dB)")
|
| 644 |
|
| 645 |
# Encode & ship
|
| 646 |
audio_b64, total_samples, channels = wav_bytes_base64(loop, int(self.params.target_sr))
|
|
|
|
| 659 |
}
|
| 660 |
chunk = JamChunk(index=self.idx, audio_base64=audio_b64, metadata=meta)
|
| 661 |
|
|
|
|
| 662 |
with self._cv:
|
| 663 |
self._outbox[self.idx] = chunk
|
| 664 |
self._cv.notify_all()
|
| 665 |
|
|
|
|
| 666 |
print(f"[emit idx={self.idx}] slice [{start}:{end}] (len={end-start}), spool_written={self._spool_written}")
|
|
|
|
| 667 |
self.idx += 1
|
| 668 |
|
| 669 |
+
# Apply pending splices/reseeds immediately after a completed emit
|
| 670 |
with self._lock:
|
| 671 |
if self._pending_token_splice is not None:
|
| 672 |
spliced = self._coerce_tokens(self._pending_token_splice["tokens"])
|
| 673 |
try:
|
| 674 |
+
self.state.context_tokens = spliced
|
| 675 |
self._pending_token_splice = None
|
| 676 |
print(f"[emit idx={self.idx}] installed token splice (in-place)")
|
| 677 |
except Exception:
|
|
|
|
| 690 |
self._pending_reseed = None
|
| 691 |
print(f"[emit idx={self.idx}] performed full reseed")
|
| 692 |
|
| 693 |
+
|
| 694 |
# ---------- main loop ----------
|
| 695 |
|
| 696 |
def run(self):
|