Fixes and Changes again

This commit is contained in:
2026-02-22 17:28:27 +02:00
parent 8abb65ac8d
commit ae4b31328f
11 changed files with 1617 additions and 96 deletions

View File

@@ -101,6 +101,8 @@ export default function PlaybackModal({ open, melody, builtMelody, files, archet
const info = melody?.information || {};
const minSpeed = info.minSpeed || null;
const maxSpeed = info.maxSpeed || null;
// Note assignments: maps note index → bell number to fire
const noteAssignments = melody?.default_settings?.noteAssignments || [];
const [steps, setSteps] = useState([]);
const [loading, setLoading] = useState(false);
@@ -152,10 +154,10 @@ export default function PlaybackModal({ open, melody, builtMelody, files, archet
return;
}
// Fall back to binary fetch
// Fall back to binary fetch — prefer uploaded file, then legacy melody.url
const binaryUrl = builtMelody?.binary_url
? `/api${builtMelody.binary_url}`
: files?.binary_url || null;
: files?.binary_url || melody?.url || null;
if (!binaryUrl) {
setLoadError("No binary or archetype data available for this melody.");
@@ -196,7 +198,28 @@ export default function PlaybackModal({ open, melody, builtMelody, files, archet
}
const ctx = ensureAudioCtx();
const stepValue = currentSteps[playFrom];
const rawStepValue = currentSteps[playFrom];
// Apply note assignments: each note in the step maps to an assigned bell number
// noteAssignments[noteIndex] = bellNumber (1-based). We rebuild the step value
// using assigned bells instead of the raw ones.
let stepValue = rawStepValue;
if (noteAssignments.length > 0) {
// Determine which notes (1-based) are active in this step
const activeNotes = [];
for (let bit = 0; bit < 16; bit++) {
if (rawStepValue & (1 << bit)) activeNotes.push(bit + 1);
}
// For each active note, look up the noteAssignment by note index (note-1)
// noteAssignments array is indexed by note position (0-based)
stepValue = 0;
for (const note of activeNotes) {
const assignedBell = noteAssignments[note - 1];
const bellToFire = (assignedBell && assignedBell > 0) ? assignedBell : note;
stepValue |= 1 << (bellToFire - 1);
}
}
setCurrentStep(playFrom);
playStep(ctx, stepValue, BEAT_DURATION_MS);
@@ -299,27 +322,65 @@ export default function PlaybackModal({ open, melody, builtMelody, files, archet
)}
</div>
{/* Bell visualizer */}
<div className="flex flex-wrap gap-1.5">
{Array.from({ length: maxBell }, (_, i) => i + 1).map((b) => {
const isActive = currentBells.includes(b);
const isUsed = allBellsUsed.has(b);
return (
<div
key={b}
className="w-7 h-7 rounded-full flex items-center justify-center text-xs font-bold transition-all"
style={{
backgroundColor: isActive ? "var(--accent)" : isUsed ? "var(--bg-card-hover)" : "var(--bg-primary)",
color: isActive ? "var(--bg-primary)" : isUsed ? "var(--text-secondary)" : "var(--border-primary)",
border: `2px solid ${isActive ? "var(--accent)" : "var(--border-primary)"}`,
transform: isActive ? "scale(1.2)" : "scale(1)",
}}
>
{b}
</div>
);
})}
</div>
{/* Note + Assignment visualizer */}
{noteAssignments.length > 0 ? (
<div>
<p className="text-xs mb-2" style={mutedStyle}>Note Assigned Bell</p>
<div className="flex flex-wrap gap-1.5">
{noteAssignments.map((assignedBell, noteIdx) => {
const noteNum = noteIdx + 1;
// A note is active if the current step has this note bit set (raw)
const isActive = currentStep >= 0 && Boolean(steps[currentStep] & (1 << (noteNum - 1)));
return (
<div
key={noteIdx}
className="flex flex-col items-center rounded-md border transition-all"
style={{
minWidth: "36px",
padding: "4px 6px",
backgroundColor: isActive ? "var(--accent)" : "var(--bg-card-hover)",
borderColor: isActive ? "var(--accent)" : "var(--border-primary)",
transform: isActive ? "scale(1.1)" : "scale(1)",
}}
>
<span className="text-xs font-bold leading-tight" style={{ color: isActive ? "var(--bg-primary)" : "var(--text-secondary)" }}>
{noteNum}
</span>
<div className="w-full my-0.5" style={{ height: "1px", backgroundColor: isActive ? "rgba(255,255,255,0.4)" : "var(--border-primary)" }} />
<span className="text-xs leading-tight" style={{ color: isActive ? "var(--bg-primary)" : "var(--text-muted)" }}>
{assignedBell > 0 ? assignedBell : "—"}
</span>
</div>
);
})}
</div>
<div className="flex gap-3 mt-1">
<span className="text-xs" style={mutedStyle}>Top = Note, Bottom = Bell</span>
</div>
</div>
) : (
/* Fallback: simple bell circles when no assignments */
<div className="flex flex-wrap gap-1.5">
{Array.from({ length: maxBell }, (_, i) => i + 1).map((b) => {
const isActive = currentBells.includes(b);
const isUsed = allBellsUsed.has(b);
return (
<div
key={b}
className="w-7 h-7 rounded-full flex items-center justify-center text-xs font-bold transition-all"
style={{
backgroundColor: isActive ? "var(--accent)" : isUsed ? "var(--bg-card-hover)" : "var(--bg-primary)",
color: isActive ? "var(--bg-primary)" : isUsed ? "var(--text-secondary)" : "var(--border-primary)",
border: `2px solid ${isActive ? "var(--accent)" : "var(--border-primary)"}`,
transform: isActive ? "scale(1.2)" : "scale(1)",
}}
>
{b}
</div>
);
})}
</div>
)}
{/* Play / Stop */}
<div className="flex items-center gap-3">
@@ -329,7 +390,7 @@ export default function PlaybackModal({ open, melody, builtMelody, files, archet
className="px-5 py-2 text-sm rounded-md font-medium transition-colors"
style={{ backgroundColor: "var(--btn-primary)", color: "var(--text-white)" }}
>
Play
Play
</button>
) : (
<button
@@ -337,7 +398,7 @@ export default function PlaybackModal({ open, melody, builtMelody, files, archet
className="px-5 py-2 text-sm rounded-md font-medium transition-colors"
style={{ backgroundColor: "var(--danger-btn)", color: "var(--text-white)" }}
>
Stop
Stop
</button>
)}
<span className="text-xs" style={mutedStyle}>Loops continuously</span>