Spaces:
Running
Running
RemiFabre Claude Opus 4.6 commited on
Commit Β·
ebf6e04
1
Parent(s): dbc544f
Fix recording sync, auto-fill name from WAV, refresh moves list
Browse files- Apply AUDIO_LEAD_MS compensation during recording too: start uploaded
audio 320ms before capture so the user hears it in sync. Without this,
the recorded motion was 320ms ahead of the audio content, causing
desync on playback.
- Auto-fill recording name from uploaded WAV filename (upload + robot).
- Refresh moves list when mode transitions from busy to idle, so newly
recorded moves appear immediately without needing a page reload.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- marionette/recording.py +10 -1
- marionette/static/main.js +11 -1
marionette/recording.py
CHANGED
|
@@ -218,11 +218,20 @@ class RecordingMixin:
|
|
| 218 |
|
| 219 |
# When using uploaded audio, don't record from mic (audio comes from the file)
|
| 220 |
should_record_mic = request.record_audio and not request.uploaded_audio_path
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 221 |
try:
|
| 222 |
timestamps, frames, audio_frames, audio_samplerate = self._capture_motion(
|
| 223 |
reachy_mini, stop_event, request.duration, should_record_mic,
|
| 224 |
record_motion=request.record_motion,
|
| 225 |
-
on_capture_start=(audio_start.set if audio_thread is not None else None),
|
| 226 |
)
|
| 227 |
finally:
|
| 228 |
# Unblock the audio thread's start_signal.wait() first, then
|
|
|
|
| 218 |
|
| 219 |
# When using uploaded audio, don't record from mic (audio comes from the file)
|
| 220 |
should_record_mic = request.record_audio and not request.uploaded_audio_path
|
| 221 |
+
|
| 222 |
+
# Start uploaded audio BEFORE capture so the user hears it in sync.
|
| 223 |
+
# The same AUDIO_LEAD_MS compensation as playback: audio needs this
|
| 224 |
+
# head-start to traverse the GStreamer pipeline before reaching the
|
| 225 |
+
# speaker. Without this, the user hears the audio ~320ms late and
|
| 226 |
+
# their recorded motion ends up 320ms ahead of the audio content.
|
| 227 |
+
if audio_thread is not None:
|
| 228 |
+
audio_start.set()
|
| 229 |
+
time.sleep(AUDIO_LEAD_MS / 1000.0)
|
| 230 |
+
|
| 231 |
try:
|
| 232 |
timestamps, frames, audio_frames, audio_samplerate = self._capture_motion(
|
| 233 |
reachy_mini, stop_event, request.duration, should_record_mic,
|
| 234 |
record_motion=request.record_motion,
|
|
|
|
| 235 |
)
|
| 236 |
finally:
|
| 237 |
# Unblock the audio thread's start_signal.wait() first, then
|
marionette/static/main.js
CHANGED
|
@@ -203,8 +203,10 @@ function updateUI(s) {
|
|
| 203 |
$modeBadge.dataset.mode = s.mode;
|
| 204 |
}
|
| 205 |
|
| 206 |
-
// ββ Busy flag ββ
|
|
|
|
| 207 |
busy = !['idle', 'queued'].includes(s.mode);
|
|
|
|
| 208 |
|
| 209 |
// ββ Record buttons state ββ
|
| 210 |
const activeEntry = s.datasets?.entries?.find(e => e.id === s.datasets?.active_id);
|
|
@@ -825,6 +827,10 @@ async function uploadAudioFile(file) {
|
|
| 825 |
if (data.duration && $recDuration) {
|
| 826 |
$recDuration.value = (Math.round(data.duration * 10) / 10).toFixed(1);
|
| 827 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
| 828 |
} catch (e) {
|
| 829 |
$audioUploadStatus.textContent = 'Failed: ' + e.message;
|
| 830 |
uploadedAudioId = null;
|
|
@@ -890,6 +896,10 @@ async function selectRobotAudio() {
|
|
| 890 |
if (data.duration && $recDuration) {
|
| 891 |
$recDuration.value = (Math.round(data.duration * 10) / 10).toFixed(1);
|
| 892 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
| 893 |
} catch (e) {
|
| 894 |
console.error('Robot audio select error:', e);
|
| 895 |
}
|
|
|
|
| 203 |
$modeBadge.dataset.mode = s.mode;
|
| 204 |
}
|
| 205 |
|
| 206 |
+
// ββ Busy flag + refresh moves after recording/playback completes ββ
|
| 207 |
+
const wasBusy = busy;
|
| 208 |
busy = !['idle', 'queued'].includes(s.mode);
|
| 209 |
+
if (wasBusy && !busy) movesListDirty = true;
|
| 210 |
|
| 211 |
// ββ Record buttons state ββ
|
| 212 |
const activeEntry = s.datasets?.entries?.find(e => e.id === s.datasets?.active_id);
|
|
|
|
| 827 |
if (data.duration && $recDuration) {
|
| 828 |
$recDuration.value = (Math.round(data.duration * 10) / 10).toFixed(1);
|
| 829 |
}
|
| 830 |
+
// Auto-fill recording name from the audio filename (without extension)
|
| 831 |
+
if (!$recName.value.trim()) {
|
| 832 |
+
$recName.value = file.name.replace(/\.[^.]+$/, '');
|
| 833 |
+
}
|
| 834 |
} catch (e) {
|
| 835 |
$audioUploadStatus.textContent = 'Failed: ' + e.message;
|
| 836 |
uploadedAudioId = null;
|
|
|
|
| 896 |
if (data.duration && $recDuration) {
|
| 897 |
$recDuration.value = (Math.round(data.duration * 10) / 10).toFixed(1);
|
| 898 |
}
|
| 899 |
+
// Auto-fill recording name from the robot audio filename (without extension)
|
| 900 |
+
if (!$recName.value.trim() && data.filename) {
|
| 901 |
+
$recName.value = data.filename.replace(/\.[^.]+$/, '');
|
| 902 |
+
}
|
| 903 |
} catch (e) {
|
| 904 |
console.error('Robot audio select error:', e);
|
| 905 |
}
|