diff --git a/.DS_Store b/.DS_Store index 58fd241..a387ba1 100644 Binary files a/.DS_Store and b/.DS_Store differ diff --git a/config.json b/config.json index dff296d..ced5c1d 100644 --- a/config.json +++ b/config.json @@ -1,6 +1,6 @@ { "instrument": "guitar", - "tuning": ["Db","Gb","Db","Ab","Eb","B"], - "frets": 4, + "tuning": ["C#", "F#", "B", "G#", "B", "D#"], + "frets": 7, "max_fingers": 4 } diff --git a/intervals.py b/intervals.py index 0a959fe..ca0c222 100644 --- a/intervals.py +++ b/intervals.py @@ -6,11 +6,8 @@ from triad import build_note_map NOTE_INDEX, _ = build_note_map() def load_config(path="config.json"): - print(f"Loading config from: {path}") # DEBUG with open(path, "r") as f: - config = json.load(f) - print(f"Loaded config: {config}") # DEBUG - return config + return json.load(f) def interval_name(semitones): interval_map = { @@ -35,13 +32,9 @@ def generate_interval_pairs(config): interval_data = [] pair_count_before_filter = 0 - print(f"Number of strings (num_strings): {num_strings}") # DEBUG: Print num_strings value - - for size in range(2, num_strings + 1): # Support chord shapes of size 2 to full string count + for size in range(2, num_strings + 1): for string_group in combinations(range(num_strings), size): - print(f"String group: {string_group}") # DEBUG - for fret_group in product(range(6), repeat=size): - print(f" Fret group: {fret_group}") # DEBUG + for fret_group in product(range(6), repeat=size): pair_count_before_filter += 1 pairwise_intervals = [] @@ -63,8 +56,8 @@ def generate_interval_pairs(config): }) export_json(interval_data, "interval_triads") - print(f"Generated {len(interval_data)} interval triads with max_frets={max_frets} and {num_strings} strings.") # Changed print message - print(f"Total pairs generated before shape filter: {pair_count_before_filter}") # Renamed print message + print(f"Generated {len(interval_data)} interval triads (max_frets={max_frets}, strings={num_strings}).") + print(f"Total pairs before filtering: {pair_count_before_filter}") return interval_data def main(): @@ -72,4 +65,4 @@ def main(): generate_interval_pairs(config) if __name__ == "__main__": - main() \ No newline at end of file + main() diff --git a/main.py b/main.py index 216391a..aad6110 100644 --- a/main.py +++ b/main.py @@ -27,7 +27,6 @@ def render_chords_html(): html = template.render(chords=filtered_chords, max_fret=max_fret, num_strings=num_strings, config=config) - # Inject theme stylesheet link theme_link = '' html = html.replace('', f'{theme_link}') diff --git a/requirements.txt b/requirements.txt index 8ea7c2a..e69de29 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +0,0 @@ -Jinja2==3.1.6 -MarkupSafe==3.0.2 diff --git a/template.html b/template.html index 197e48d..b7ac1f4 100644 --- a/template.html +++ b/template.html @@ -9,7 +9,7 @@

Matched Chord Positions

-
+
{% for match in chords %}

{{ match.chord }}

@@ -27,7 +27,7 @@
{% endfor %}
- + \ No newline at end of file diff --git a/triad.py b/triad.py index 6193ba5..ad6247e 100644 --- a/triad.py +++ b/triad.py @@ -1,8 +1,7 @@ -# triads.py import os import json from itertools import product, combinations -from utils import load_config, export_json # Import from utils +from utils import load_config, export_json def build_note_map(): base_notes = ['C', 'C#', 'D', 'D#', 'E', 'F', @@ -20,11 +19,8 @@ def build_note_map(): def load_json(name): path = os.path.join("generated_data", f"{name}.json") - print(f"Loading JSON from: {path}") with open(path, "r") as f: - data = json.load(f) - print(f"Loaded data from {name}.json: {data}") - return data + return json.load(f) def count_effective_fingers(fingering, num_strings): fretted = [(i, int(f)) for i, f in enumerate(fingering) if f not in ("x", "X", "0")] @@ -71,38 +67,23 @@ def find_chord_fingerings(config): NUM_STRINGS = len(string_tunings) MAX_FRET = config.get("frets", 4) MAX_FINGERS = config.get("max_fingers", 3) - print(f"MAX_FRET: {MAX_FRET}, MAX_FINGERS: {MAX_FINGERS}") all_chords = {} - for chord_type, chord_group in chords.items(): # Corrected loop - print(f"Processing chord group: {chord_type}") # Optional debug print - for chord_name_in_group, intervals in chord_group.items(): # Iterate through chords in each group - full_chord_name = f"{chord_name_in_group.capitalize()} {chord_type.capitalize()[:-1]}" # Corrected chord name: "Major Triad" -> "Major" and capitalize names + for chord_type, chord_group in chords.items(): + for chord_name_in_group, intervals in chord_group.items(): + full_chord_name = f"{chord_name_in_group.capitalize()} {chord_type.capitalize()[:-1]}" all_chords[full_chord_name] = { "intervals": intervals, "type": chord_type, "name": chord_name_in_group } - print("All chords to search:", all_chords) # Print the FINAL all_chords dictionary - print(f"Using tuning: {config.get('tuning')}") # DEBUG: Print tuning - note_map, reverse_note_map = build_note_map() - fret_options_no_x = [str(fret) for fret in range(MAX_FRET + 1)] + ["x"] - print("Starting chord processing loop...") for chord_name, chord_data in all_chords.items(): intervals = chord_data["intervals"] - print(f"\n--- Processing chord: {chord_name}, intervals: {intervals} ---") interval_set = set(intervals) - print(f" Target interval set (semitones): {interval_set}") # DEBUG - - semitone_intervals_needed = set() # Assuming intervals in chord_definitions are names, convert to semitones - for interval_value in intervals: # Now intervals are already semitones from chord_definitions.json - semitone_intervals_needed.add(interval_value) # Use interval_value directly - - interval_set = semitone_intervals_needed # Now interval_set is in semitones, and correctly populated for test_fingering_tuple in product(fret_options_no_x, repeat=NUM_STRINGS): test_fingering = list(test_fingering_tuple) @@ -110,7 +91,7 @@ def find_chord_fingerings(config): fretted_notes_semitones = [] for i, fret in enumerate(test_fingering): if fret not in ("x", "X"): - tuning_note = string_tunings[i].strip() # Changed here + tuning_note = string_tunings[i].strip() note_semitone = (note_map[tuning_note] + int(fret)) % 12 fretted_notes_semitones.append(note_semitone) @@ -123,33 +104,29 @@ def find_chord_fingerings(config): if not is_valid_mute_config(test_fingering) or count_effective_fingers(test_fingering, NUM_STRINGS) > MAX_FINGERS: continue - unique_fretted_notes = sorted(list(set(fretted_notes_semitones))) # Get unique notes for root check + unique_fretted_notes = sorted(list(set(fretted_notes_semitones))) if len(unique_fretted_notes) < len(intervals): continue - for potential_root_semitone in unique_fretted_notes: # Iterate through unique notes as potential roots + for potential_root_semitone in unique_fretted_notes: intervals_in_fingering = set() for note_semitone in fretted_notes_semitones: interval = (note_semitone - potential_root_semitone) % 12 intervals_in_fingering.add(interval) - print(f" Fingering: {test_fingering}, Notes (semitones): {fretted_notes_semitones}, Potential Root: {reverse_note_map.get(potential_root_semitone)}, Intervals in Fingering: {intervals_in_fingering}, Required Intervals: {interval_set}") # ADD THIS - - if intervals_in_fingering == interval_set: # Changed to EXACT MATCH for primary chords + if intervals_in_fingering == interval_set: fingering_tuple = tuple(test_fingering) if fingering_tuple not in generated_fingerings: - root_note_name_for_chord = reverse_note_map.get(potential_root_semitone, str(potential_root_semitone)) # Get root note name for chord name - result_chord_name = f"{root_note_name_for_chord} {chord_name}" # Correctly formatted chord name + root_note_name = reverse_note_map.get(potential_root_semitone, str(potential_root_semitone)) + result_chord_name = f"{root_note_name} {chord_name}" result = { - "chord": result_chord_name, # Use the correctly formatted chord name + "chord": result_chord_name, "fingering": test_fingering, "intervals": list(interval_set), - "interval_set": interval_set # Added new key for interval set + "interval_set": interval_set } - # if count_effective_fingers(test_fingering) < MAX_FINGERS: - # continue def detect_barres(fingering): fretted = [(i, int(f)) for i, f in enumerate(fingering) if f not in ("x", "X", "0")] if not fretted: @@ -167,52 +144,43 @@ def find_chord_fingerings(config): ): barres.append({"fret": fret, "strings": strings}) return barres - - result["barres"] = detect_barres(test_fingering) + result["barres"] = detect_barres(test_fingering) results.append(result) generated_fingerings.add(fingering_tuple) - print(f" Chord FOUND (EXACT MATCH): {result}") # ADD THIS - break # Stop after finding exact match for a root - + break def count_fingers(fingering): return sum(1 for f in fingering if f not in ("x", "X", "0")) - def is_same_chord(fingering, chord_name, string_tunings, note_map, intervals): # intervals is interval_set here - print(f" [is_same_chord] Checking fingering: {fingering}, chord_name: {chord_name}, intervals: {intervals}") # DEBUG + def is_same_chord(fingering, chord_name, string_tunings, note_map, intervals): fretted = [] for i, f in enumerate(fingering): if f not in ("x", "X"): tuning_note = string_tunings[i].strip() note = (note_map[tuning_note] + int(f)) % 12 fretted.append(note) - print(f" [is_same_chord] Fretted notes (semitones): {fretted}") # DEBUG for root in set(fretted): - interval_set_fingering = set() # rename to avoid confusion + interval_set_fingering = set() for note in fretted: interval_set_fingering.add((note - root) % 12) - print(f" [is_same_chord] Potential root: {reverse_note_map.get(root)}, Interval set from fingering: {interval_set_fingering}") # DEBUG - if interval_set_fingering == intervals: # Exact match - print(f" [is_same_chord] EXACT MATCH FOUND for root {reverse_note_map.get(root)}") # DEBUG + if interval_set_fingering == intervals: return True - print(" [is_same_chord] NO MATCH FOUND") # DEBUG return False def is_open_chord(fingering): return all(f in ("0", "x", "X") for f in fingering) - - # Final global filter pass for safety + # Filter valid configurations results = [ r for r in results if is_valid_mute_config(r["fingering"]) and count_effective_fingers(r["fingering"], NUM_STRINGS) <= MAX_FINGERS ] - # Group results + # Group fingerings by chord name grouped = {} for r in results: - chord_key = r["chord"].replace(" (alt)", "") # Group alternatives with primary chords + chord_key = r["chord"].replace(" (alt)", "") grouped.setdefault(chord_key, []).append(r["fingering"]) final_results = [] @@ -221,7 +189,7 @@ def find_chord_fingerings(config): checked = set() primary = None alternatives = [] - has_fretted_primary = False # Flag to track if a fretted primary has been found + has_fretted_primary = False for fingering in fingerings: key = tuple(fingering) @@ -231,55 +199,48 @@ def find_chord_fingerings(config): intervals = next((r["intervals"] for r in results if r["fingering"] == list(fingering) and r["chord"] == chord_name), []) intervals = set(intervals) - is_exact = is_same_chord(fingering, chord_name, string_tunings, note_map, intervals) # Check for exact match + is_exact = is_same_chord(fingering, chord_name, string_tunings, note_map, intervals) is_open = is_open_chord(fingering) - is_fretted = not is_open # define fretted as not open + is_fretted = not is_open - - if is_exact: # Prioritize exact matches - if is_fretted: # If it's a fretted exact match, it's the best primary - if not has_fretted_primary: # if no fretted primary yet, set it. + if is_exact: + if is_fretted: + if not has_fretted_primary: primary = fingering - has_fretted_primary = True # Mark that we found a fretted primary - elif not has_fretted_primary: # if it's an open exact match and no fretted primary yet, consider it primary for now, but can be replaced - if primary is None: # if no primary yet (and no fretted primary), set open as primary tentatively + has_fretted_primary = True + elif not has_fretted_primary: + if primary is None: primary = fingering - else: # if it's an open chord and we already HAVE a fretted primary, just add as alternative. + else: alternatives.append(fingering) - - - elif not is_exact: # If not an exact match, consider as alternative if primary is already set (exact match found) or if we have ANY primary set. + else: if primary is not None: alternatives.append(fingering) - elif primary is None and not alternatives: # if no primary and no alternatives yet, set as primary if nothing better is found + elif primary is None and not alternatives: primary = fingering else: alternatives.append(fingering) - - simpler_versions = [] - if primary is not None and fingering == primary: # Only find simpler versions for the primary fingering + # Find simpler muted variations of the primary + if primary is not None and fingering == primary: for n in range(1, len(fingering)): for idxs in combinations(range(len(fingering)), n): test = fingering[:] for i in idxs: test[i] = "x" - if is_valid_mute_config(test) and is_same_chord(test, chord_name, string_tunings, note_map, intervals): # use intervals here - simpler_versions.append(list(test)) # Convert tuple to list - alternatives.extend(simpler_versions) # Add simpler versions to alternatives + if is_valid_mute_config(test) and is_same_chord(test, chord_name, string_tunings, note_map, intervals): + alternatives.append(list(test)) - - if primary is None and alternatives: # If no primary after all checks, pick first alternative as primary (shouldn't happen often with exact match priority, but for safety) + if primary is None and alternatives: primary = alternatives.pop(0) - if primary is not None: # Ensure we have a primary fingering before adding to final results + if primary is not None: final_results.append({ - "chord": chord_name.replace(" Triad", "").replace(" Seventh", "").replace(" Sixth", "").replace(" Ext.", ""), # Clean up chord name + "chord": chord_name.replace(" Triad", "").replace(" Seventh", "").replace(" Sixth", "").replace(" Ext.", ""), "fingering": primary, - "alternatives": [alt for alt in set(map(tuple, alternatives)) if list(alt) != primary and count_fingers(list(alt)) <= MAX_FINGERS] # Remove duplicates and primary from alternatives and filter by max fingers + "alternatives": [alt for alt in set(map(tuple, alternatives)) if list(alt) != primary and count_fingers(list(alt)) <= MAX_FINGERS] }) - return final_results def generate_chord_positions(): @@ -290,9 +251,7 @@ def convert_to_tab(chord_results): for chord in chord_results: fretted_notes = chord.get('fingering', []) chord_name = chord.get('chord', 'Unknown') - tab = list(fretted_notes) - chord_tabs.append({ "chord": chord_name, "tab": tab @@ -302,11 +261,11 @@ def main(): config = load_config() fingerings = find_chord_fingerings(config) if not fingerings: - print("No fingerings found. Check input data and logic.") + print("No fingerings found.") else: - print("Found", len(fingerings), "fingerings.") - export_json(fingerings, "triad_chords") # Use utils.export_json + print(f"Found {len(fingerings)} fingerings.") + export_json(fingerings, "triad_chords") print(json.dumps(fingerings, indent=2)) if __name__ == "__main__": - main() \ No newline at end of file + main() diff --git a/utils.py b/utils.py index 000d261..365b22b 100644 --- a/utils.py +++ b/utils.py @@ -1,4 +1,3 @@ -# web-tuner/utils.py import json import os @@ -6,7 +5,7 @@ def load_config(path="config.json"): with open(path) as f: return json.load(f) -def export_json(data, name, output_dir="generated_data"): # Added output_dir as parameter with default +def export_json(data, name, output_dir="generated_data"): os.makedirs(output_dir, exist_ok=True) path = os.path.join(output_dir, f"{name}.json") with open(path, "w") as f: diff --git a/www/.DS_Store b/www/.DS_Store deleted file mode 100644 index 5008ddf..0000000 Binary files a/www/.DS_Store and /dev/null differ diff --git a/www/chords-light.css b/www/chords-light.css index f9bad9e..9ffb417 100644 --- a/www/chords-light.css +++ b/www/chords-light.css @@ -134,9 +134,8 @@ pointer-events: none; z-index: 2; width: 1rem; - left: 0; /* Barre line starts from the left edge of the column */ - /* right: 0; Remove right: 0 to allow left positioning to control */ - margin: 0; /* Remove auto margins */ + left: 0; + margin: 0; display: flex; justify-content: center; align-items: center; diff --git a/www/tuner.css b/www/tuner.css index e39ce42..c097d70 100644 --- a/www/tuner.css +++ b/www/tuner.css @@ -5,15 +5,16 @@ body { align-items: center; min-height: 100vh; margin: 0; - background-color: #f4f4f4; + background-color: #000000; } .container { - background-color: #fff; + background-color: #ffffff0c; padding: 30px; border-radius: 8px; box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1); text-align: center; + color: #dfdfdf; } h1 { @@ -46,17 +47,18 @@ h1 { .strings { display: flex; gap: 10px; - margin-bottom: 20px; + margin: 1rem; justify-content: center; } .string-button { padding: 15px 25px; + margin: .5rem; font-size: 1.2em; border: none; border-radius: 5px; - background-color: #4CAF50; /* Green */ - color: white; + background-color: #00000000; /* Green */ + color: #dfdfdf; cursor: pointer; transition: background-color 0.3s; } @@ -71,7 +73,7 @@ h1 { border: none; border-radius: 5px; background-color: #008CBA; /* Blue */ - color: white; + color: #dfdfdf; cursor: pointer; transition: background-color 0.3s; } @@ -83,4 +85,50 @@ h1 { #output { margin-top: 20px; font-weight: bold; +} + +.instrument-select { + margin: 1rem; +} + +label[for=instrument] { + color: #dfdfdf; + font-weight: 600; +} + +#instrument { + padding: .5rem; + padding-right: 1rem; + padding-left: 1rem; + font-size: 12pt; + font-weight: bold; + border: 2px solid rgb(67, 66, 66); + color: #dfdfdf; + border-radius: 5px; +} + +.tuning-controls { + padding: .5rem; + padding-right: 1rem; + padding-left: 1rem; + font-size: 12pt; + font-weight: bold; + color: #dfdfdf; +} + +.a440-control, .transpose-control, .tuning-mode-select, .tuning-select{ + padding: .5rem; + padding-right: 1rem; + padding-left: 1rem; + font-size: 12pt; + font-weight: bold; + border: 2px solid rgb(67, 66, 66); + background-color: black; + color: #dfdfdf; + border-radius: 5px; +} + +#tuning, #tuning-mode, #transpose, #a440, #instrument { + background-color: black; + color: #dfdfdf; } \ No newline at end of file diff --git a/www/tuner.html b/www/tuner.html index 0e06912..69c499c 100644 --- a/www/tuner.html +++ b/www/tuner.html @@ -6,7 +6,7 @@
-

Ukulele Tuner

+

Ukulele Tuner

@@ -73,6 +73,7 @@ +
diff --git a/www/tuner.js b/www/tuner.js index 4c062b4..bd60f60 100644 --- a/www/tuner.js +++ b/www/tuner.js @@ -1,19 +1,18 @@ document.addEventListener('DOMContentLoaded', () => { - // Initialize Tone.js - Tone.start(); // Ensure audio context starts on user interaction + Tone.start(); const synth = new Tone.Synth().toDestination(); const a440Input = document.getElementById('a440'); const transposeInput = document.getElementById('transpose'); const instrumentSelect = document.getElementById('instrument'); + const instlabel = document.getElementById('instrument-label'); const tuningSelect = document.getElementById('tuning'); - const tuningModeSelect = document.getElementById('tuning-mode'); // New tuning mode selector + const tuningModeSelect = document.getElementById('tuning-mode'); const stringsDiv = document.getElementById('strings'); const playAllButton = document.getElementById('play-all'); const outputDiv = document.getElementById('output'); - // Expanded tunings for multiple instruments const instrumentTunings = { "ukulele": { "standard": [67 + 12, 60 + 12, 64 + 12, 69 + 12], // G5, C5, E5, A5 @@ -47,7 +46,8 @@ document.addEventListener('DOMContentLoaded', () => { "nashville-high-strung": [40, 45, 50, 55, 59, 64], // EADGBE but with lighter strings "orkney": [36, 40, 43, 36, 40, 43], // CGDGCD "modal-tuning-1": [40, 45, 39, 50, 45, 64], // CGDGBE - "modal-tuning-2": [40, 45, 37, 50, 45, 64] // EAEAC#E + "modal-tuning-2": [40, 45, 37, 50, 45, 64], // EAEAC#E + "db-custom": [49, 54, 59, 56, 71, 63] // Db Gb B Ab B (oct) Eb } }; let currentTuning = []; @@ -74,6 +74,8 @@ document.addEventListener('DOMContentLoaded', () => { function updateInstrument() { const selectedInstrument = instrumentSelect.value; tuningSelect.innerHTML = ""; // Clear previous tuning options + let inst = instrumentSelect.value; + instlabel.innerText = inst.charAt(0).toUpperCase() + inst.slice(1) + " Tuner"; Object.keys(instrumentTunings[selectedInstrument]).forEach(tuning => { let option = document.createElement("option"); option.value = tuning; @@ -102,8 +104,8 @@ document.addEventListener('DOMContentLoaded', () => { } function calculateFrequency(midiNote, tuningMode) { - const referenceNote = 60; // Middle C in MIDI - const referenceFreq = currentA440 * Math.pow(2, (referenceNote - 69) / 12); // Middle C in A440 + const referenceNote = 60; + const referenceFreq = currentA440 * Math.pow(2, (referenceNote - 69) / 12); if (tuningMode === "harmonic") { const noteNames = ["C", "Db", "D", "Eb", "E", "F", "Gb", "G", "Ab", "A", "Bb", "B"]; @@ -129,18 +131,11 @@ document.addEventListener('DOMContentLoaded', () => { const adjustedMidiNote = midiNote + currentTranspose; const frequency = calculateFrequency(adjustedMidiNote, tuningMode); - // Adjust frequency based on A440 reference (simplified - for precise tuning, more complex calculations needed) const referenceFrequencyRatio = currentA440 / 440; const adjustedFrequency = frequency * referenceFrequencyRatio; synth.set({ oscillator: { type: 'sine' } }); - synth.triggerAttackRelease(adjustedFrequency, "1.75s"); // Play for 2 seconds duration - - // **DEBUGGING OUTPUTS ADDED HERE** - console.log("MIDI Note (input):", midiNote); - console.log("Adjusted MIDI Note (transpose applied):", adjustedMidiNote); - console.log("Calculated Frequency (before A440 adjust):", frequency); - console.log("Final Frequency (A440 adjusted):", adjustedFrequency); + synth.triggerAttackRelease(adjustedFrequency, "1.75s"); outputDiv.textContent = `Playing: ${Tone.Frequency(adjustedFrequency).toNote()} (Freq: ${adjustedFrequency.toFixed(2)} Hz, A4 Ref: ${currentA440} Hz, Transpose: ${currentTranspose} semitones)`; } @@ -161,7 +156,7 @@ document.addEventListener('DOMContentLoaded', () => { instrumentSelect.addEventListener('change', updateInstrument); tuningSelect.addEventListener('change', updateTuning); tuningModeSelect.addEventListener('change', () => { - tuningMode = tuningModeSelect.value; // Update tuning mode based on user selection + tuningMode = tuningModeSelect.value; }); updateInstrument(); // Initialize instrument and tuning on page load