diff --git a/webapp/server.py b/webapp/server.py index fc4f40d..c7659c0 100644 --- a/webapp/server.py +++ b/webapp/server.py @@ -19,29 +19,25 @@ from fractions import Fraction app = Flask(__name__) -# Path to data files +# Path to output directory DATA_DIR = Path(__file__).parent.parent / "output" -DATA_FILE = "output_chords.json" # default file +DEFAULT_FILE = "output_chords.json" # default file # State current_index = 0 chords = [] -dims = (2, 3, 5, 7) # OSC settings fundamental = 110.0 osc_sender = OSCSender(ip="127.0.0.1", port=57120, fundamental=fundamental) -def get_chords_file(): - return DATA_DIR / DATA_FILE - - -def load_chords(): +def load_chords(filepath=None): global chords, current_index - chords_file = get_chords_file() - if chords_file.exists(): - with open(chords_file) as f: + if filepath is None: + filepath = DATA_DIR / DEFAULT_FILE + if filepath.exists(): + with open(filepath) as f: data = json.load(f) chords = data.get("chords", []) current_index = 0 @@ -72,82 +68,6 @@ def calculate_cents(fraction_str): return 1200 * math.log2(fr) -def calculate_graph(chord): - """Calculate nodes and edges for a chord using pitch logic.""" - if not chord: - return {"nodes": [], "edges": []} - - # Calculate cents for each pitch - import math - - nodes = [] - cents_list = [] - - for i, pitch in enumerate(chord): - fr = parse_fraction(pitch.get("fraction", "1")) - cents = 1200 * math.log2(fr) if fr > 0 else 0 - cents_list.append(cents) - - nodes.append( - { - "id": i, - "cents": round(cents), - "fraction": pitch.get("fraction", "1"), - "hs_array": pitch.get("hs_array", []), - } - ) - - # Find edges: differ by ±1 in exactly one dimension (ignoring dim 0) - edges = [] - for i in range(len(chord)): - for j in range(i + 1, len(chord)): - hs1 = chord[i].get("hs_array", []) - hs2 = chord[j].get("hs_array", []) - - if not hs1 or not hs2: - continue - - # Count differences in dims 1, 2, 3 - diff_count = 0 - diff_dim = -1 - - for d in range(1, len(hs1)): - diff = hs2[d] - hs1[d] - if abs(diff) == 1: - diff_count += 1 - diff_dim = d - elif diff != 0: - break # diff > 1 in this dimension - else: - # Check if exactly one dimension differs - if diff_count == 1 and diff_dim > 0: - # Calculate frequency ratio from pitch difference - # diff = hs1 - hs2 gives direction - # Convert to fraction - diff_hs = [hs1[d] - hs2[d] for d in range(len(hs1))] - - numerator = 1 - denominator = 1 - for d_idx, d in enumerate(dims): - exp = diff_hs[d_idx] - if exp > 0: - numerator *= d**exp - elif exp < 0: - denominator *= d ** (-exp) - - ratio = ( - f"{numerator}/{denominator}" - if denominator > 1 - else str(numerator) - ) - - edges.append( - {"source": i, "target": j, "ratio": ratio, "dim": diff_dim} - ) - - return {"nodes": nodes, "edges": edges} - - @app.route("/") def index(): return send_from_directory(".", "path_navigator.html") @@ -158,126 +78,11 @@ def serve_static(filename): return send_from_directory(".", filename) -@app.route("/api/files") -def list_files(): - """List available output files.""" - files = [] - if DATA_DIR.exists(): - for f in DATA_DIR.iterdir(): - if f.is_file() and f.suffix == ".json" and "chords" in f.name: - files.append(f.name) - return jsonify({"files": sorted(files)}) - - -@app.route("/api/set-file/", methods=["POST"]) -def set_data_file(filename): - global DATA_FILE, current_index - DATA_FILE = filename - current_index = 0 - load_chords() - return jsonify({"file": DATA_FILE, "loaded": len(chords), "index": current_index}) - - @app.route("/api/chords") def get_chords(): return jsonify({"chords": chords, "total": len(chords)}) -@app.route("/api/current") -def get_current(): - if not chords: - return jsonify({"error": "No chords loaded"}), 404 - - prev_idx = current_index - 1 if current_index > 0 else None - next_idx = current_index + 1 if current_index < len(chords) - 1 else None - - return jsonify( - { - "index": current_index, - "total": len(chords), - "prev": chords[prev_idx] if prev_idx is not None else None, - "current": chords[current_index], - "next": chords[next_idx] if next_idx is not None else None, - "has_prev": prev_idx is not None, - "has_next": next_idx is not None, - } - ) - - -@app.route("/api/graph/") -def get_graph(index): - """Get computed graph for prev/current/next at given index.""" - if not chords: - return jsonify({"error": "No chords loaded"}), 404 - - if not (0 <= index < len(chords)): - return jsonify({"error": "Invalid index"}), 400 - - prev_idx = index - 1 if index > 0 else None - next_idx = index + 1 if index < len(chords) - 1 else None - - return jsonify( - { - "index": index, - "total": len(chords), - "prev": calculate_graph(chords[prev_idx]) if prev_idx is not None else None, - "current": calculate_graph(chords[index]), - "next": calculate_graph(chords[next_idx]) if next_idx is not None else None, - "has_prev": prev_idx is not None, - "has_next": next_idx is not None, - } - ) - - -@app.route("/api/all-graphs") -def get_all_graphs(): - """Get computed graph for ALL chords at once for single-canvas rendering.""" - if not chords: - return jsonify({"error": "No chords loaded"}), 404 - - all_graphs = [] - for i, chord in enumerate(chords): - graph = calculate_graph(chord) - graph["index"] = i - all_graphs.append(graph) - - return jsonify( - { - "total": len(chords), - "graphs": all_graphs, - } - ) - - -@app.route("/api/navigate", methods=["POST"]) -def navigate(): - global current_index - data = request.json - direction = data.get("direction", "next") - - if direction == "prev" and current_index > 0: - current_index -= 1 - elif direction == "next" and current_index < len(chords) - 1: - current_index += 1 - - return jsonify({"index": current_index}) - - -@app.route("/api/goto/") -def goto(index): - global current_index - if 0 <= index < len(chords): - current_index = index - return jsonify({"index": current_index}) - return jsonify({"error": "Invalid index"}), 400 - - -@app.route("/api/reload", methods=["POST"]) -def reload(): - load_chords() - return jsonify({"loaded": len(chords), "index": current_index}) - - @app.route("/api/set-fundamental", methods=["POST"]) def set_fundamental(): """Set the fundamental frequency for OSC playback.""" @@ -439,7 +244,8 @@ def batch_calculate_cents_api(): if __name__ == "__main__": print("Starting Path Navigator server...") - print(f"Loading chords from: {get_chords_file()}") + filepath = DATA_DIR / DEFAULT_FILE + print(f"Loading chords from: {filepath}") load_chords() print(f"Loaded {len(chords)} chords") app.run(host="0.0.0.0", port=8080, debug=True)