This is the only commit that will still be the python version. I'm switching this to Go. But I felt it would be best to have one archival commit for it lest I lose track later on. Godspeed.
This commit is contained in:
parent
a786c88bb9
commit
3fb782c52b
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"instrument": "guitar",
|
"instrument": "guitar",
|
||||||
"tuning": ["Db","Gb","Db","Ab","Eb","B"],
|
"tuning": ["C#", "F#", "B", "G#", "B", "D#"],
|
||||||
"frets": 4,
|
"frets": 7,
|
||||||
"max_fingers": 4
|
"max_fingers": 4
|
||||||
}
|
}
|
||||||
|
|
|
||||||
19
intervals.py
19
intervals.py
|
|
@ -6,11 +6,8 @@ from triad import build_note_map
|
||||||
NOTE_INDEX, _ = build_note_map()
|
NOTE_INDEX, _ = build_note_map()
|
||||||
|
|
||||||
def load_config(path="config.json"):
|
def load_config(path="config.json"):
|
||||||
print(f"Loading config from: {path}") # DEBUG
|
|
||||||
with open(path, "r") as f:
|
with open(path, "r") as f:
|
||||||
config = json.load(f)
|
return json.load(f)
|
||||||
print(f"Loaded config: {config}") # DEBUG
|
|
||||||
return config
|
|
||||||
|
|
||||||
def interval_name(semitones):
|
def interval_name(semitones):
|
||||||
interval_map = {
|
interval_map = {
|
||||||
|
|
@ -35,13 +32,9 @@ def generate_interval_pairs(config):
|
||||||
interval_data = []
|
interval_data = []
|
||||||
pair_count_before_filter = 0
|
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):
|
||||||
|
|
||||||
for size in range(2, num_strings + 1): # Support chord shapes of size 2 to full string count
|
|
||||||
for string_group in combinations(range(num_strings), size):
|
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):
|
||||||
for fret_group in product(range(6), repeat=size):
|
|
||||||
print(f" Fret group: {fret_group}") # DEBUG
|
|
||||||
pair_count_before_filter += 1
|
pair_count_before_filter += 1
|
||||||
|
|
||||||
pairwise_intervals = []
|
pairwise_intervals = []
|
||||||
|
|
@ -63,8 +56,8 @@ def generate_interval_pairs(config):
|
||||||
})
|
})
|
||||||
|
|
||||||
export_json(interval_data, "interval_triads")
|
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"Generated {len(interval_data)} interval triads (max_frets={max_frets}, strings={num_strings}).")
|
||||||
print(f"Total pairs generated before shape filter: {pair_count_before_filter}") # Renamed print message
|
print(f"Total pairs before filtering: {pair_count_before_filter}")
|
||||||
return interval_data
|
return interval_data
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
|
|
@ -72,4 +65,4 @@ def main():
|
||||||
generate_interval_pairs(config)
|
generate_interval_pairs(config)
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
|
|
|
||||||
1
main.py
1
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)
|
html = template.render(chords=filtered_chords, max_fret=max_fret, num_strings=num_strings, config=config)
|
||||||
|
|
||||||
# Inject theme stylesheet link
|
|
||||||
theme_link = '<link id="theme-stylesheet" rel="stylesheet" href="chords-default.css">'
|
theme_link = '<link id="theme-stylesheet" rel="stylesheet" href="chords-default.css">'
|
||||||
html = html.replace('<head>', f'<head>{theme_link}')
|
html = html.replace('<head>', f'<head>{theme_link}')
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,2 +0,0 @@
|
||||||
Jinja2==3.1.6
|
|
||||||
MarkupSafe==3.0.2
|
|
||||||
|
|
@ -9,7 +9,7 @@
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<h1>Matched Chord Positions</h1>
|
<h1>Matched Chord Positions</h1>
|
||||||
<div id="chord-container" data-max-fret="{{ max_fret }}" data-num-strings="{{ num_strings }}">
|
<div id="chord-container" data-max-fret="{{ max_fret }}" data-num-strings="{{ num_strings }}" data-tuning='{{ tuning_data_json | safe }}'>
|
||||||
{% for match in chords %}
|
{% for match in chords %}
|
||||||
<div class="chord-card">
|
<div class="chord-card">
|
||||||
<h2>{{ match.chord }}</h2>
|
<h2>{{ match.chord }}</h2>
|
||||||
|
|
@ -27,7 +27,7 @@
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
<!-- JavaScript will load config.json to determine string count and tunings, and render fretboards dynamically -->
|
|
||||||
<script src="chords.js"></script>
|
<script src="chords.js"></script>
|
||||||
|
<script src="http://unpkg.com/tone"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
131
triad.py
131
triad.py
|
|
@ -1,8 +1,7 @@
|
||||||
# triads.py
|
|
||||||
import os
|
import os
|
||||||
import json
|
import json
|
||||||
from itertools import product, combinations
|
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():
|
def build_note_map():
|
||||||
base_notes = ['C', 'C#', 'D', 'D#', 'E', 'F',
|
base_notes = ['C', 'C#', 'D', 'D#', 'E', 'F',
|
||||||
|
|
@ -20,11 +19,8 @@ def build_note_map():
|
||||||
|
|
||||||
def load_json(name):
|
def load_json(name):
|
||||||
path = os.path.join("generated_data", f"{name}.json")
|
path = os.path.join("generated_data", f"{name}.json")
|
||||||
print(f"Loading JSON from: {path}")
|
|
||||||
with open(path, "r") as f:
|
with open(path, "r") as f:
|
||||||
data = json.load(f)
|
return json.load(f)
|
||||||
print(f"Loaded data from {name}.json: {data}")
|
|
||||||
return data
|
|
||||||
|
|
||||||
def count_effective_fingers(fingering, num_strings):
|
def count_effective_fingers(fingering, num_strings):
|
||||||
fretted = [(i, int(f)) for i, f in enumerate(fingering) if f not in ("x", "X", "0")]
|
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)
|
NUM_STRINGS = len(string_tunings)
|
||||||
MAX_FRET = config.get("frets", 4)
|
MAX_FRET = config.get("frets", 4)
|
||||||
MAX_FINGERS = config.get("max_fingers", 3)
|
MAX_FINGERS = config.get("max_fingers", 3)
|
||||||
print(f"MAX_FRET: {MAX_FRET}, MAX_FINGERS: {MAX_FINGERS}")
|
|
||||||
|
|
||||||
all_chords = {}
|
all_chords = {}
|
||||||
for chord_type, chord_group in chords.items(): # Corrected loop
|
for chord_type, chord_group in chords.items():
|
||||||
print(f"Processing chord group: {chord_type}") # Optional debug print
|
for chord_name_in_group, intervals in chord_group.items():
|
||||||
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]}"
|
||||||
full_chord_name = f"{chord_name_in_group.capitalize()} {chord_type.capitalize()[:-1]}" # Corrected chord name: "Major Triad" -> "Major" and capitalize names
|
|
||||||
all_chords[full_chord_name] = {
|
all_chords[full_chord_name] = {
|
||||||
"intervals": intervals,
|
"intervals": intervals,
|
||||||
"type": chord_type,
|
"type": chord_type,
|
||||||
"name": chord_name_in_group
|
"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()
|
note_map, reverse_note_map = build_note_map()
|
||||||
|
|
||||||
fret_options_no_x = [str(fret) for fret in range(MAX_FRET + 1)] + ["x"]
|
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():
|
for chord_name, chord_data in all_chords.items():
|
||||||
intervals = chord_data["intervals"]
|
intervals = chord_data["intervals"]
|
||||||
print(f"\n--- Processing chord: {chord_name}, intervals: {intervals} ---")
|
|
||||||
interval_set = set(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):
|
for test_fingering_tuple in product(fret_options_no_x, repeat=NUM_STRINGS):
|
||||||
test_fingering = list(test_fingering_tuple)
|
test_fingering = list(test_fingering_tuple)
|
||||||
|
|
@ -110,7 +91,7 @@ def find_chord_fingerings(config):
|
||||||
fretted_notes_semitones = []
|
fretted_notes_semitones = []
|
||||||
for i, fret in enumerate(test_fingering):
|
for i, fret in enumerate(test_fingering):
|
||||||
if fret not in ("x", "X"):
|
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
|
note_semitone = (note_map[tuning_note] + int(fret)) % 12
|
||||||
fretted_notes_semitones.append(note_semitone)
|
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:
|
if not is_valid_mute_config(test_fingering) or count_effective_fingers(test_fingering, NUM_STRINGS) > MAX_FINGERS:
|
||||||
continue
|
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):
|
if len(unique_fretted_notes) < len(intervals):
|
||||||
continue
|
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()
|
intervals_in_fingering = set()
|
||||||
for note_semitone in fretted_notes_semitones:
|
for note_semitone in fretted_notes_semitones:
|
||||||
interval = (note_semitone - potential_root_semitone) % 12
|
interval = (note_semitone - potential_root_semitone) % 12
|
||||||
intervals_in_fingering.add(interval)
|
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:
|
||||||
|
|
||||||
if intervals_in_fingering == interval_set: # Changed to EXACT MATCH for primary chords
|
|
||||||
fingering_tuple = tuple(test_fingering)
|
fingering_tuple = tuple(test_fingering)
|
||||||
if fingering_tuple not in generated_fingerings:
|
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
|
root_note_name = reverse_note_map.get(potential_root_semitone, str(potential_root_semitone))
|
||||||
result_chord_name = f"{root_note_name_for_chord} {chord_name}" # Correctly formatted chord name
|
result_chord_name = f"{root_note_name} {chord_name}"
|
||||||
result = {
|
result = {
|
||||||
"chord": result_chord_name, # Use the correctly formatted chord name
|
"chord": result_chord_name,
|
||||||
"fingering": test_fingering,
|
"fingering": test_fingering,
|
||||||
"intervals": list(interval_set),
|
"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):
|
def detect_barres(fingering):
|
||||||
fretted = [(i, int(f)) for i, f in enumerate(fingering) if f not in ("x", "X", "0")]
|
fretted = [(i, int(f)) for i, f in enumerate(fingering) if f not in ("x", "X", "0")]
|
||||||
if not fretted:
|
if not fretted:
|
||||||
|
|
@ -167,52 +144,43 @@ def find_chord_fingerings(config):
|
||||||
):
|
):
|
||||||
barres.append({"fret": fret, "strings": strings})
|
barres.append({"fret": fret, "strings": strings})
|
||||||
return barres
|
return barres
|
||||||
|
|
||||||
result["barres"] = detect_barres(test_fingering)
|
|
||||||
|
|
||||||
|
result["barres"] = detect_barres(test_fingering)
|
||||||
results.append(result)
|
results.append(result)
|
||||||
generated_fingerings.add(fingering_tuple)
|
generated_fingerings.add(fingering_tuple)
|
||||||
print(f" Chord FOUND (EXACT MATCH): {result}") # ADD THIS
|
break
|
||||||
break # Stop after finding exact match for a root
|
|
||||||
|
|
||||||
|
|
||||||
def count_fingers(fingering):
|
def count_fingers(fingering):
|
||||||
return sum(1 for f in fingering if f not in ("x", "X", "0"))
|
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
|
def is_same_chord(fingering, chord_name, string_tunings, note_map, intervals):
|
||||||
print(f" [is_same_chord] Checking fingering: {fingering}, chord_name: {chord_name}, intervals: {intervals}") # DEBUG
|
|
||||||
fretted = []
|
fretted = []
|
||||||
for i, f in enumerate(fingering):
|
for i, f in enumerate(fingering):
|
||||||
if f not in ("x", "X"):
|
if f not in ("x", "X"):
|
||||||
tuning_note = string_tunings[i].strip()
|
tuning_note = string_tunings[i].strip()
|
||||||
note = (note_map[tuning_note] + int(f)) % 12
|
note = (note_map[tuning_note] + int(f)) % 12
|
||||||
fretted.append(note)
|
fretted.append(note)
|
||||||
print(f" [is_same_chord] Fretted notes (semitones): {fretted}") # DEBUG
|
|
||||||
for root in set(fretted):
|
for root in set(fretted):
|
||||||
interval_set_fingering = set() # rename to avoid confusion
|
interval_set_fingering = set()
|
||||||
for note in fretted:
|
for note in fretted:
|
||||||
interval_set_fingering.add((note - root) % 12)
|
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:
|
||||||
if interval_set_fingering == intervals: # Exact match
|
|
||||||
print(f" [is_same_chord] EXACT MATCH FOUND for root {reverse_note_map.get(root)}") # DEBUG
|
|
||||||
return True
|
return True
|
||||||
print(" [is_same_chord] NO MATCH FOUND") # DEBUG
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def is_open_chord(fingering):
|
def is_open_chord(fingering):
|
||||||
return all(f in ("0", "x", "X") for f in fingering)
|
return all(f in ("0", "x", "X") for f in fingering)
|
||||||
|
|
||||||
|
# Filter valid configurations
|
||||||
# Final global filter pass for safety
|
|
||||||
results = [
|
results = [
|
||||||
r for r in results
|
r for r in results
|
||||||
if is_valid_mute_config(r["fingering"]) and count_effective_fingers(r["fingering"], NUM_STRINGS) <= MAX_FINGERS
|
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 = {}
|
grouped = {}
|
||||||
for r in results:
|
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"])
|
grouped.setdefault(chord_key, []).append(r["fingering"])
|
||||||
|
|
||||||
final_results = []
|
final_results = []
|
||||||
|
|
@ -221,7 +189,7 @@ def find_chord_fingerings(config):
|
||||||
checked = set()
|
checked = set()
|
||||||
primary = None
|
primary = None
|
||||||
alternatives = []
|
alternatives = []
|
||||||
has_fretted_primary = False # Flag to track if a fretted primary has been found
|
has_fretted_primary = False
|
||||||
|
|
||||||
for fingering in fingerings:
|
for fingering in fingerings:
|
||||||
key = tuple(fingering)
|
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 = next((r["intervals"] for r in results if r["fingering"] == list(fingering) and r["chord"] == chord_name), [])
|
||||||
intervals = set(intervals)
|
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_open = is_open_chord(fingering)
|
||||||
is_fretted = not is_open # define fretted as not open
|
is_fretted = not is_open
|
||||||
|
|
||||||
|
if is_exact:
|
||||||
if is_exact: # Prioritize exact matches
|
if is_fretted:
|
||||||
if is_fretted: # If it's a fretted exact match, it's the best primary
|
if not has_fretted_primary:
|
||||||
if not has_fretted_primary: # if no fretted primary yet, set it.
|
|
||||||
primary = fingering
|
primary = fingering
|
||||||
has_fretted_primary = True # Mark that we found a fretted primary
|
has_fretted_primary = True
|
||||||
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
|
elif not has_fretted_primary:
|
||||||
if primary is None: # if no primary yet (and no fretted primary), set open as primary tentatively
|
if primary is None:
|
||||||
primary = fingering
|
primary = fingering
|
||||||
else: # if it's an open chord and we already HAVE a fretted primary, just add as alternative.
|
else:
|
||||||
alternatives.append(fingering)
|
alternatives.append(fingering)
|
||||||
|
else:
|
||||||
|
|
||||||
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.
|
|
||||||
if primary is not None:
|
if primary is not None:
|
||||||
alternatives.append(fingering)
|
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
|
primary = fingering
|
||||||
else:
|
else:
|
||||||
alternatives.append(fingering)
|
alternatives.append(fingering)
|
||||||
|
|
||||||
|
# Find simpler muted variations of the primary
|
||||||
simpler_versions = []
|
if primary is not None and fingering == primary:
|
||||||
if primary is not None and fingering == primary: # Only find simpler versions for the primary fingering
|
|
||||||
for n in range(1, len(fingering)):
|
for n in range(1, len(fingering)):
|
||||||
for idxs in combinations(range(len(fingering)), n):
|
for idxs in combinations(range(len(fingering)), n):
|
||||||
test = fingering[:]
|
test = fingering[:]
|
||||||
for i in idxs:
|
for i in idxs:
|
||||||
test[i] = "x"
|
test[i] = "x"
|
||||||
if is_valid_mute_config(test) and is_same_chord(test, chord_name, string_tunings, note_map, intervals): # use intervals here
|
if is_valid_mute_config(test) and is_same_chord(test, chord_name, string_tunings, note_map, intervals):
|
||||||
simpler_versions.append(list(test)) # Convert tuple to list
|
alternatives.append(list(test))
|
||||||
alternatives.extend(simpler_versions) # Add simpler versions to alternatives
|
|
||||||
|
|
||||||
|
if primary is None and alternatives:
|
||||||
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)
|
|
||||||
primary = alternatives.pop(0)
|
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({
|
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,
|
"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
|
return final_results
|
||||||
|
|
||||||
def generate_chord_positions():
|
def generate_chord_positions():
|
||||||
|
|
@ -290,9 +251,7 @@ def convert_to_tab(chord_results):
|
||||||
for chord in chord_results:
|
for chord in chord_results:
|
||||||
fretted_notes = chord.get('fingering', [])
|
fretted_notes = chord.get('fingering', [])
|
||||||
chord_name = chord.get('chord', 'Unknown')
|
chord_name = chord.get('chord', 'Unknown')
|
||||||
|
|
||||||
tab = list(fretted_notes)
|
tab = list(fretted_notes)
|
||||||
|
|
||||||
chord_tabs.append({
|
chord_tabs.append({
|
||||||
"chord": chord_name,
|
"chord": chord_name,
|
||||||
"tab": tab
|
"tab": tab
|
||||||
|
|
@ -302,11 +261,11 @@ def main():
|
||||||
config = load_config()
|
config = load_config()
|
||||||
fingerings = find_chord_fingerings(config)
|
fingerings = find_chord_fingerings(config)
|
||||||
if not fingerings:
|
if not fingerings:
|
||||||
print("No fingerings found. Check input data and logic.")
|
print("No fingerings found.")
|
||||||
else:
|
else:
|
||||||
print("Found", len(fingerings), "fingerings.")
|
print(f"Found {len(fingerings)} fingerings.")
|
||||||
export_json(fingerings, "triad_chords") # Use utils.export_json
|
export_json(fingerings, "triad_chords")
|
||||||
print(json.dumps(fingerings, indent=2))
|
print(json.dumps(fingerings, indent=2))
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
|
|
|
||||||
3
utils.py
3
utils.py
|
|
@ -1,4 +1,3 @@
|
||||||
# web-tuner/utils.py
|
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
|
@ -6,7 +5,7 @@ def load_config(path="config.json"):
|
||||||
with open(path) as f:
|
with open(path) as f:
|
||||||
return json.load(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)
|
os.makedirs(output_dir, exist_ok=True)
|
||||||
path = os.path.join(output_dir, f"{name}.json")
|
path = os.path.join(output_dir, f"{name}.json")
|
||||||
with open(path, "w") as f:
|
with open(path, "w") as f:
|
||||||
|
|
|
||||||
Binary file not shown.
|
|
@ -134,9 +134,8 @@
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
width: 1rem;
|
width: 1rem;
|
||||||
left: 0; /* Barre line starts from the left edge of the column */
|
left: 0;
|
||||||
/* right: 0; Remove right: 0 to allow left positioning to control */
|
margin: 0;
|
||||||
margin: 0; /* Remove auto margins */
|
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
|
||||||
|
|
@ -5,15 +5,16 @@ body {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
background-color: #f4f4f4;
|
background-color: #000000;
|
||||||
}
|
}
|
||||||
|
|
||||||
.container {
|
.container {
|
||||||
background-color: #fff;
|
background-color: #ffffff0c;
|
||||||
padding: 30px;
|
padding: 30px;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
|
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
color: #dfdfdf;
|
||||||
}
|
}
|
||||||
|
|
||||||
h1 {
|
h1 {
|
||||||
|
|
@ -46,17 +47,18 @@ h1 {
|
||||||
.strings {
|
.strings {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
margin-bottom: 20px;
|
margin: 1rem;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.string-button {
|
.string-button {
|
||||||
padding: 15px 25px;
|
padding: 15px 25px;
|
||||||
|
margin: .5rem;
|
||||||
font-size: 1.2em;
|
font-size: 1.2em;
|
||||||
border: none;
|
border: none;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
background-color: #4CAF50; /* Green */
|
background-color: #00000000; /* Green */
|
||||||
color: white;
|
color: #dfdfdf;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: background-color 0.3s;
|
transition: background-color 0.3s;
|
||||||
}
|
}
|
||||||
|
|
@ -71,7 +73,7 @@ h1 {
|
||||||
border: none;
|
border: none;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
background-color: #008CBA; /* Blue */
|
background-color: #008CBA; /* Blue */
|
||||||
color: white;
|
color: #dfdfdf;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: background-color 0.3s;
|
transition: background-color 0.3s;
|
||||||
}
|
}
|
||||||
|
|
@ -83,4 +85,50 @@ h1 {
|
||||||
#output {
|
#output {
|
||||||
margin-top: 20px;
|
margin-top: 20px;
|
||||||
font-weight: bold;
|
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;
|
||||||
}
|
}
|
||||||
|
|
@ -6,7 +6,7 @@
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<h1>Ukulele Tuner</h1>
|
<h1 id="instrument-label">Ukulele Tuner</h1>
|
||||||
|
|
||||||
<div class="instrument-select">
|
<div class="instrument-select">
|
||||||
<label for="instrument">Select Instrument:</label>
|
<label for="instrument">Select Instrument:</label>
|
||||||
|
|
@ -73,6 +73,7 @@
|
||||||
<option value="orkney">Orkney (CGDGCD)</option>
|
<option value="orkney">Orkney (CGDGCD)</option>
|
||||||
<option value="modal-tuning-1">Modal 1 (CGDGBE)</option>
|
<option value="modal-tuning-1">Modal 1 (CGDGBE)</option>
|
||||||
<option value="modal-tuning-2">Modal 2 (EAEAC#E)</option>
|
<option value="modal-tuning-2">Modal 2 (EAEAC#E)</option>
|
||||||
|
<option value="db-custom">Db Custom (Db Gb B Ab B Eb)</option>
|
||||||
</optgroup>
|
</optgroup>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
27
www/tuner.js
27
www/tuner.js
|
|
@ -1,19 +1,18 @@
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
// Initialize Tone.js
|
Tone.start();
|
||||||
Tone.start(); // Ensure audio context starts on user interaction
|
|
||||||
|
|
||||||
const synth = new Tone.Synth().toDestination();
|
const synth = new Tone.Synth().toDestination();
|
||||||
|
|
||||||
const a440Input = document.getElementById('a440');
|
const a440Input = document.getElementById('a440');
|
||||||
const transposeInput = document.getElementById('transpose');
|
const transposeInput = document.getElementById('transpose');
|
||||||
const instrumentSelect = document.getElementById('instrument');
|
const instrumentSelect = document.getElementById('instrument');
|
||||||
|
const instlabel = document.getElementById('instrument-label');
|
||||||
const tuningSelect = document.getElementById('tuning');
|
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 stringsDiv = document.getElementById('strings');
|
||||||
const playAllButton = document.getElementById('play-all');
|
const playAllButton = document.getElementById('play-all');
|
||||||
const outputDiv = document.getElementById('output');
|
const outputDiv = document.getElementById('output');
|
||||||
|
|
||||||
// Expanded tunings for multiple instruments
|
|
||||||
const instrumentTunings = {
|
const instrumentTunings = {
|
||||||
"ukulele": {
|
"ukulele": {
|
||||||
"standard": [67 + 12, 60 + 12, 64 + 12, 69 + 12], // G5, C5, E5, A5
|
"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
|
"nashville-high-strung": [40, 45, 50, 55, 59, 64], // EADGBE but with lighter strings
|
||||||
"orkney": [36, 40, 43, 36, 40, 43], // CGDGCD
|
"orkney": [36, 40, 43, 36, 40, 43], // CGDGCD
|
||||||
"modal-tuning-1": [40, 45, 39, 50, 45, 64], // CGDGBE
|
"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 = [];
|
let currentTuning = [];
|
||||||
|
|
@ -74,6 +74,8 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||||
function updateInstrument() {
|
function updateInstrument() {
|
||||||
const selectedInstrument = instrumentSelect.value;
|
const selectedInstrument = instrumentSelect.value;
|
||||||
tuningSelect.innerHTML = ""; // Clear previous tuning options
|
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 => {
|
Object.keys(instrumentTunings[selectedInstrument]).forEach(tuning => {
|
||||||
let option = document.createElement("option");
|
let option = document.createElement("option");
|
||||||
option.value = tuning;
|
option.value = tuning;
|
||||||
|
|
@ -102,8 +104,8 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
function calculateFrequency(midiNote, tuningMode) {
|
function calculateFrequency(midiNote, tuningMode) {
|
||||||
const referenceNote = 60; // Middle C in MIDI
|
const referenceNote = 60;
|
||||||
const referenceFreq = currentA440 * Math.pow(2, (referenceNote - 69) / 12); // Middle C in A440
|
const referenceFreq = currentA440 * Math.pow(2, (referenceNote - 69) / 12);
|
||||||
|
|
||||||
if (tuningMode === "harmonic") {
|
if (tuningMode === "harmonic") {
|
||||||
const noteNames = ["C", "Db", "D", "Eb", "E", "F", "Gb", "G", "Ab", "A", "Bb", "B"];
|
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 adjustedMidiNote = midiNote + currentTranspose;
|
||||||
const frequency = calculateFrequency(adjustedMidiNote, tuningMode);
|
const frequency = calculateFrequency(adjustedMidiNote, tuningMode);
|
||||||
|
|
||||||
// Adjust frequency based on A440 reference (simplified - for precise tuning, more complex calculations needed)
|
|
||||||
const referenceFrequencyRatio = currentA440 / 440;
|
const referenceFrequencyRatio = currentA440 / 440;
|
||||||
const adjustedFrequency = frequency * referenceFrequencyRatio;
|
const adjustedFrequency = frequency * referenceFrequencyRatio;
|
||||||
|
|
||||||
synth.set({ oscillator: { type: 'sine' } });
|
synth.set({ oscillator: { type: 'sine' } });
|
||||||
synth.triggerAttackRelease(adjustedFrequency, "1.75s"); // Play for 2 seconds duration
|
synth.triggerAttackRelease(adjustedFrequency, "1.75s");
|
||||||
|
|
||||||
// **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);
|
|
||||||
|
|
||||||
outputDiv.textContent = `Playing: ${Tone.Frequency(adjustedFrequency).toNote()} (Freq: ${adjustedFrequency.toFixed(2)} Hz, A4 Ref: ${currentA440} Hz, Transpose: ${currentTranspose} semitones)`;
|
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);
|
instrumentSelect.addEventListener('change', updateInstrument);
|
||||||
tuningSelect.addEventListener('change', updateTuning);
|
tuningSelect.addEventListener('change', updateTuning);
|
||||||
tuningModeSelect.addEventListener('change', () => {
|
tuningModeSelect.addEventListener('change', () => {
|
||||||
tuningMode = tuningModeSelect.value; // Update tuning mode based on user selection
|
tuningMode = tuningModeSelect.value;
|
||||||
});
|
});
|
||||||
updateInstrument(); // Initialize instrument and tuning on page load
|
updateInstrument(); // Initialize instrument and tuning on page load
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue