From 8f332fac527d3a9c1c44ce7c571db7b04bf520e3 Mon Sep 17 00:00:00 2001 From: Michael Winter Date: Wed, 1 Apr 2026 16:44:20 +0200 Subject: [PATCH] Add batch API endpoints for cents calculation, client fetches cents from server --- webapp/path_navigator.html | 49 +++++++++++++++++++++++--------------- webapp/server.py | 39 ++++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+), 19 deletions(-) diff --git a/webapp/path_navigator.html b/webapp/path_navigator.html index d4854a4..ea7a588 100644 --- a/webapp/path_navigator.html +++ b/webapp/path_navigator.html @@ -581,13 +581,37 @@ // Load from Flask API - get chords and compute graphs client-side async function loadAllGraphs() { try { + // Step 1: Get raw chord data const response = await fetch("/api/chords"); if (!response.ok) throw new Error("API not available"); const data = await response.json(); - // Compute graphs from raw chord data client-side + // Step 2: Collect all fractions from all chords + const allFractions = []; + const chordFractions = []; // Track which fractions belong to which chord + + for (const chord of data.chords) { + const fractions = []; + for (const pitch of chord) { + fractions.push(pitch.fraction || "1"); + } + chordFractions.push(fractions); + allFractions.push(...fractions); + } + + // Step 3: Batch fetch cents from server (avoid N+1 problem) + const centsResponse = await fetch("/api/batch-calculate-cents", { + method: "POST", + headers: {"Content-Type": "application/json"}, + body: JSON.stringify({ fractions: allFractions }) + }); + const centsData = await centsResponse.json(); + const allCents = centsData.results.map(r => r.cents); + + // Step 4: Build graphs with cached cents values + let centsIndex = 0; const graphs = data.chords.map((chord, index) => { - return calculateGraph(chord, index); + return calculateGraph(chord, index, () => allCents[centsIndex++]); }); allGraphsData = { @@ -611,18 +635,17 @@ } } - // Compute graph (nodes + edges) from raw chord data - client-side version - function calculateGraph(chord, index) { + // Compute graph (nodes + edges) from raw chord data - using API for cents + function calculateGraph(chord, index, getNextCent) { if (!chord) return { nodes: [], edges: [] }; const nodes = []; const dims = [2, 3, 5, 7]; - // Calculate cents for each pitch + // Calculate cents for each pitch (fetched from server) for (let i = 0; i < chord.length; i++) { const pitch = chord[i]; - const fraction = parseFraction(pitch.fraction || "1"); - const cents = fraction > 0 ? 1200 * Math.log2(fraction) : 0; + const cents = getNextCent(); nodes.push({ id: i, @@ -689,18 +712,6 @@ return { nodes, edges, index }; } - // Parse fraction string to number - function parseFraction(fracStr) { - if (typeof fracStr === 'number') return fracStr; - if (!fracStr) return 1; - - if (fracStr.includes('/')) { - const [num, den] = fracStr.split('/').map(Number); - return num / den; - } - return Number(fracStr); - } - // Update UI elements function updateUI() { hasPrev = currentIndex > 0; diff --git a/webapp/server.py b/webapp/server.py index 47942af..2ae6a6e 100644 --- a/webapp/server.py +++ b/webapp/server.py @@ -406,6 +406,45 @@ def load_file(): return jsonify({"error": f"Invalid JSON in: {filepath}"}), 400 +@app.route("/api/parse-fraction", methods=["POST"]) +def parse_fraction_api(): + """Parse a fraction string to float.""" + data = request.json + fraction = data.get("fraction", "1") + try: + value = parse_fraction(fraction) + return jsonify({"fraction": fraction, "value": value}) + except Exception as e: + return jsonify({"error": str(e)}), 400 + + +@app.route("/api/calculate-cents", methods=["POST"]) +def calculate_cents_api(): + """Calculate cents from fraction string.""" + data = request.json + fraction = data.get("fraction", "1") + try: + cents = calculate_cents(fraction) + return jsonify({"fraction": fraction, "cents": cents}) + except Exception as e: + return jsonify({"error": str(e)}), 400 + + +@app.route("/api/batch-calculate-cents", methods=["POST"]) +def batch_calculate_cents_api(): + """Calculate cents for multiple fractions at once.""" + data = request.json + fractions = data.get("fractions", []) + try: + results = [] + for fraction in fractions: + cents = calculate_cents(fraction) + results.append({"fraction": fraction, "cents": cents}) + return jsonify({"results": results}) + except Exception as e: + return jsonify({"error": str(e)}), 400 + + if __name__ == "__main__": print("Starting Path Navigator server...") print(f"Loading chords from: {get_chords_file()}")