1 Commits
Author SHA1 Message Date
Claude 5ee7aa9e7d Music: Loop switch to leave out the loop marker
Custom music restarts after about 10 seconds on the home screen even with a
60-second clip, though the file holds the whole clip. Turning Loop off writes
the clip without the smpl loop chunk, to see if the PS5 then plays it all.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01B3zQD8DZVn9N7NJ6NHPLGg
2026-10-11 19:43:08 +00:00
2 changed files with 8 additions and 5 deletions

No files matched your search

+3 -2
View File
@@ -414,7 +414,7 @@ static void encode_music(Staged &s) {
static json music_json(const Staged &s) {
double total = (double)s.audio.pcm.size() / std::max(1, s.audio.channels) / std::max(1, s.audio.rate);
return {{"ok", true}, {"note", s.note}, {"seconds", s.enc.seconds}, {"total", total}, {"kb", (int)(s.enc.at9.size() / 1024)},
{"start", s.opt.start_sec}, {"duration", s.opt.duration_sec},
{"start", s.opt.start_sec}, {"duration", s.opt.duration_sec}, {"loop", s.opt.loop},
{"summary", secs(s.opt.start_sec) + " to " + secs(s.opt.start_sec + s.enc.seconds) + ", " + std::to_string(s.enc.at9.size() / 1024) + " KB"}};
}
static json stage_music(const std::vector<uint8_t> &bytes) {
@@ -427,7 +427,7 @@ static json stage_music(const std::vector<uint8_t> &bytes) {
g_staged["music"] = std::move(s);
return j;
}
// Start (seconds) and length (seconds; 0 = the longest that fits in the PS5's 2 MB limit).
// Start (seconds), length (seconds; 0 = the longest that fits in the PS5's 2 MB limit) and loop.
static json music_clip(const json &req) {
Staged s;
{
@@ -438,6 +438,7 @@ static json music_clip(const json &req) {
}
s.opt.start_sec = std::max(0.0, req.value("start", s.opt.start_sec));
s.opt.duration_sec = std::max(0.0, req.value("duration", s.opt.duration_sec));
s.opt.loop = req.value("loop", s.opt.loop); // off = no loop marker; the PS5 decides what happens at the end
encode_music(s);
json j = music_json(s);
std::lock_guard<std::mutex> l(g_mu);
+5 -3
View File
@@ -191,6 +191,7 @@ button,input,select{font:inherit;color:inherit}
.mclip label{color:var(--muted);font-size:13.5px;display:flex;gap:8px;align-items:center}
.mclip input,.mclip select{padding:8px 12px;border-radius:var(--r-sm);border:1px solid var(--line);background:rgba(0,0,0,.3);color:var(--ink);font-size:14px}
.mclip input{width:80px}
.mclip input[type=checkbox]{width:auto;accent-color:#9fd3ff}
.mclip audio{height:38px;flex:1;min-width:240px}
.field{display:grid;gap:8px}
.field label{font-size:13.5px;color:var(--muted)}
@@ -384,6 +385,7 @@ button,input,select{font:inherit;color:inherit}
<div class="mclip" id="mclip">
<label>Start <input id="mstart" value="0:00" inputmode="numeric" title="Where the clip starts in the song (m:ss)"></label>
<label>Length <select id="mlen"><option value="15">15 s</option><option value="30">30 s</option><option value="60">60 s</option><option value="0">Longest that fits</option></select></label>
<label title="Marks the whole clip as a loop. Turn off to let the PS5 decide what happens at the end."><input type="checkbox" id="mloop" checked> Loop</label>
<audio id="maudio" controls preload="none"></audio>
</div>
</div>
@@ -730,7 +732,7 @@ async function stage(kind, file){
try {
const j = await api('/api/stage/'+kind, {method:'POST', body: await file.arrayBuffer()});
st.staged[kind] = {note: j.note || '', summary: j.summary || '', fresh: true};
if (kind === 'music') { $('#mstart').value = fmtTime(j.start); $('#mlen').value = String(j.duration); }
if (kind === 'music') { $('#mstart').value = fmtTime(j.start); $('#mlen').value = String(j.duration); $('#mloop').checked = j.loop !== false; }
} catch (err) { sl.textContent = err.message; sl.classList.add('warn'); return; }
if (kind === 'icon') markPick(null);
refreshPreview(false);
@@ -742,13 +744,13 @@ async function reclip(){
const sl = document.querySelector('.slot[data-kind="music"] .s');
sl.textContent = 'Converting…'; sl.classList.remove('warn');
try {
const j = await api('/api/music/clip', {method:'POST', body: JSON.stringify({start: parseTime($('#mstart').value), duration: Number($('#mlen').value)})});
const j = await api('/api/music/clip', {method:'POST', body: JSON.stringify({start: parseTime($('#mstart').value), duration: Number($('#mlen').value), loop: $('#mloop').checked})});
st.staged.music = {note: j.note || '', summary: j.summary || '', fresh: true};
$('#mstart').value = fmtTime(j.start);
} catch (err) { sl.textContent = err.message; sl.classList.add('warn'); return; }
refreshPreview(false);
}
$('#mstart').addEventListener('change', reclip); $('#mlen').addEventListener('change', reclip);
$('#mstart').addEventListener('change', reclip); $('#mlen').addEventListener('change', reclip); $('#mloop').addEventListener('change', reclip);
$('#afile').onchange = e => { const f = e.target.files[0]; if (f) stage('music', f); };
const isAudio = f => /^audio\//.test(f.type) || /\.(mp3|wav|flac|aiff?)$/i.test(f.name);