-
+
\ 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 @@