From e11b89a1b7cd5c4ac0cc7ada42929fd7a1b160a6 Mon Sep 17 00:00:00 2001
From: bonamin
Date: Sun, 22 Feb 2026 20:36:15 +0200
Subject: [PATCH] CODEX - Improvements to the page
---
frontend/src/melodies/MelodyComposer.jsx | 314 +++++++++++++++++++----
1 file changed, 271 insertions(+), 43 deletions(-)
diff --git a/frontend/src/melodies/MelodyComposer.jsx b/frontend/src/melodies/MelodyComposer.jsx
index 2092a93..ed1fa13 100644
--- a/frontend/src/melodies/MelodyComposer.jsx
+++ b/frontend/src/melodies/MelodyComposer.jsx
@@ -1,6 +1,9 @@
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
+import { useNavigate } from "react-router-dom";
+import api from "../api/client";
const MAX_NOTES = 16;
+const NOTE_LABELS = "ABCDEFGHIJKLMNOP";
function bellFrequency(bellNumber) {
return 880 * Math.pow(Math.pow(2, 1 / 12), -2 * (bellNumber - 1));
@@ -50,6 +53,7 @@ function playStep(audioCtx, stepValue, noteDurationMs) {
}
export default function MelodyComposer() {
+ const navigate = useNavigate();
const [steps, setSteps] = useState(Array.from({ length: 16 }, () => 0));
const [noteCount, setNoteCount] = useState(8);
const [stepDelayMs, setStepDelayMs] = useState(280);
@@ -57,6 +61,13 @@ export default function MelodyComposer() {
const [loopEnabled, setLoopEnabled] = useState(true);
const [isPlaying, setIsPlaying] = useState(false);
const [currentStep, setCurrentStep] = useState(-1);
+ const [error, setError] = useState("");
+ const [successMsg, setSuccessMsg] = useState("");
+ const [showDeployModal, setShowDeployModal] = useState(false);
+ const [deployName, setDeployName] = useState("");
+ const [deployPid, setDeployPid] = useState("");
+ const [deployError, setDeployError] = useState("");
+ const [deploying, setDeploying] = useState(false);
const audioCtxRef = useRef(null);
const playbackRef = useRef(null);
@@ -171,10 +182,74 @@ export default function MelodyComposer() {
const handlePlay = () => {
if (!stepsRef.current.length) return;
+ setError("");
setIsPlaying(true);
scheduleStep(0);
};
+ const openDeployModal = () => {
+ setError("");
+ setSuccessMsg("");
+ setDeployError("");
+ setShowDeployModal(true);
+ };
+
+ const closeDeployModal = () => {
+ if (deploying) return;
+ setDeployError("");
+ setShowDeployModal(false);
+ };
+
+ const handleDeploy = async () => {
+ const name = deployName.trim();
+ const pid = deployPid.trim();
+ if (!name) {
+ setDeployError("Name is required.");
+ return;
+ }
+ if (!pid) {
+ setDeployError("PID is required.");
+ return;
+ }
+
+ setDeploying(true);
+ setDeployError("");
+ setSuccessMsg("");
+ try {
+ const existing = await api.get("/builder/melodies");
+ const list = existing.melodies || [];
+ const dupName = list.find((m) => m.name.toLowerCase() === name.toLowerCase());
+ if (dupName) {
+ setDeployError(`An archetype with the name "${name}" already exists.`);
+ return;
+ }
+ const dupPid = list.find((m) => m.pid && m.pid.toLowerCase() === pid.toLowerCase());
+ if (dupPid) {
+ setDeployError(`An archetype with the PID "${pid}" already exists.`);
+ return;
+ }
+
+ const stepsStr = steps.map(stepToNotation).join(",");
+ const created = await api.post("/builder/melodies", {
+ name,
+ pid,
+ steps: stepsStr,
+ });
+
+ setSuccessMsg(`Archetype "${name}" deployed successfully.`);
+ setShowDeployModal(false);
+ setDeployName("");
+ setDeployPid("");
+ if (created?.id) {
+ navigate(`/melodies/archetypes/${created.id}`);
+ }
+ } catch (err) {
+ setDeployError(err.message);
+ } finally {
+ setDeploying(false);
+ }
+ };
+
const activeBellsInCurrentStep = useMemo(() => {
if (currentStep < 0 || !steps[currentStep]) return [];
const active = [];
@@ -195,6 +270,31 @@ export default function MelodyComposer() {
+ {error && (
+
+ {error}
+
+ )}
+ {successMsg && (
+
+ {successMsg}
+
+ )}
+
- Step
+ |
+ |