import numpy as np
from pydub import AudioSegment
from pydub.generators import Sine

# Define the frequencies for the basic chords
CHORD_FREQUENCIES = {
    'G': [196.00, 246.94, 392.00],  # G major chord: G, B, D
    'Em': [164.81, 196.00, 246.94],  # E minor chord: E, G, B
    'Am': [220.00, 261.63, 329.63],  # A minor chord: A, C, E
    'C': [261.63, 329.63, 392.00],  # C major chord: C, E, G
}


# Function to generate a chord sound
def generate_chord_sound(chord_name, duration_ms=1000):
    if chord_name not in CHORD_FREQUENCIES:
        raise ValueError(f"Chord {chord_name} not recognized")

    frequencies = CHORD_FREQUENCIES[chord_name]
    chord = sum(Sine(freq).to_audio_segment(duration=duration_ms) for freq in frequencies)

    # Normalize the volume of the chord
    chord = chord.apply_gain(-20)
    return chord


# Define the chord progression
chord_progression = [
    'G', 'Em', 'Am', 'C',
    'G', 'Em', 'Am', 'C',
    'G', 'Em', 'C', 'Am',
    'G', 'Em', 'C', 'Am',
    'G', 'Am', 'C', 'G',
    'G', 'Am', 'C', 'G',
    'G', 'Am', 'C', 'G',
    'G', 'Am', 'C'
]


def main():
    # Generate the full audio sequence
    full_audio = AudioSegment.silent(duration=0)

    for chord_name in chord_progression:
        chord_sound = generate_chord_sound(chord_name)
        full_audio += chord_sound

    # Export the audio to a WAV file
    full_audio.export("chord_progression.wav", format="wav")
    print("WAV file generated: chord_progression.wav")


if __name__ == "__main__":
    main()

This code works and generates sound the next code fails generating sound but might be abke to analyse it and get the correct cords used back? Combine the two

import numpy as np
from pydub import AudioSegment
from pydub.generators import Sine
import librosa
import librosa.display
import tkinter as tk
from tkinter import filedialog, messagebox
import matplotlib.pyplot as plt
import os
import threading
import simpleaudio as sa  # For playback
import sys
import shutil

# Define the sampling rate and duration
SAMPLE_RATE = 44100  # Hz
DURATION = 2  # seconds per chord

# Define chord definitions with their constituent notes
CHORDS = {
    'G': ['G4', 'B4', 'D5'],
    'Em': ['E4', 'G4', 'B4'],
    'Am': ['A4', 'C5', 'E5'],
    'C': ['C4', 'E4', 'G4']
}

# Map note names to frequencies
NOTE_FREQUENCIES = {
    'C4': 261.63,
    'C#4': 277.18,
    'D4': 293.66,
    'D#4': 311.13,
    'E4': 329.63,
    'F4': 349.23,
    'F#4': 369.99,
    'G4': 392.00,
    'G#4': 415.30,
    'A4': 440.00,
    'A#4': 466.16,
    'B4': 493.88,
    'C5': 523.25,
    'C#5': 554.37,
    'D5': 587.33,
    'D#5': 622.25,
    'E5': 659.25,
    'F5': 698.46,
    'F#5': 739.99,
    'G5': 783.99,
    'G#5': 830.61,
    'A5': 880.00,
    'A#5': 932.33,
    'B5': 987.77
}


def generate_chord(chord_name):
    """Generate a chord AudioSegment given the chord name."""
    if chord_name not in CHORDS:
        raise ValueError(f"Chord '{chord_name}' is not defined.")

    chord = CHORDS[chord_name]
    wave = AudioSegment.silent(duration=0)
    for note in chord:
        freq = NOTE_FREQUENCIES.get(note)
        if not freq:
            raise ValueError(f"Frequency for note '{note}' is not defined.")
        sine_wave = Sine(freq).to_audio_segment(duration=DURATION * 1000).apply_gain(
            -3)  # Reduce gain to prevent clipping
        wave = wave.overlay(sine_wave)
    return wave


def generate_audio_sequence():
    """Generate the audio sequence based on the specified chord progression."""
    # Define the chord progression
    progression = [
        ['G', 'Em', 'Am', 'C'],
        ['G', 'Em', 'Am', 'C'],
        ['G', 'Em', 'C', 'Am'],
        ['G', 'Em', 'C', 'Am'],
        ['G', 'Am', 'C', 'G'],
        ['G', 'Am', 'C', 'G'],
        ['G', 'Am', 'C', 'G'],
        ['G', 'Am', 'C']
    ]

    full_track = AudioSegment.silent(duration=0)
    for chord_set in progression:
        for chord in chord_set:
            full_track += generate_chord(chord)
    return full_track


def save_audio():
    """Generate and save the audio sequence to the specified file path."""
    # Ask user where to save the audio
    save_path = filedialog.asksaveasfilename(defaultextension=".wav",
                                             filetypes=[("WAV files", "*.wav")],
                                             title="Save Audio As")
    if not save_path:
        return  # User canceled

    try:
        audio = generate_audio_sequence()
        # Save as WAV
        audio.export(save_path, format="wav")
        messagebox.showinfo("Success", f"Audio saved to {save_path}")
    except Exception as e:
        messagebox.showerror("Error", f"Failed to save audio.\n{e}")


def play_audio(file_path):
    """Play the audio file using simpleaudio."""
    try:
        # Load the WAV file
        wave_obj = sa.WaveObject.from_wave_file(file_path)
        play_obj = wave_obj.play()
        play_obj.wait_done()  # Wait until sound has finished playing
    except Exception as e:
        messagebox.showerror("Error", f"Failed to play audio.\n{e}")


def detect_chords(file_path):
    """Detect chords from the audio file."""
    try:
        y, sr = librosa.load(file_path, sr=SAMPLE_RATE, mono=True)
    except Exception as e:
        messagebox.showerror("Error", f"Failed to load audio file.\n{e}")
        return

    hop_length = 512
    # Use librosa's chroma feature
    chroma = librosa.feature.chroma_stft(y=y, sr=sr, hop_length=hop_length)
    # Average chroma features over each chord duration
    frames_per_chord = int(DURATION * sr / hop_length)
    num_chords = chroma.shape[1] // frames_per_chord
    detected_chords = []
    for i in range(num_chords):
        start = i * frames_per_chord
        end = start + frames_per_chord
        chroma_slice = chroma[:, start:end]
        chroma_mean = np.mean(chroma_slice, axis=1)
        # Identify the chord based on the highest chroma components
        chord = identify_chord(chroma_mean)
        detected_chords.append(chord)
    # Handle remaining frames
    if chroma.shape[1] % frames_per_chord != 0:
        chroma_slice = chroma[:, num_chords * frames_per_chord:]
        chroma_mean = np.mean(chroma_slice, axis=1)
        chord = identify_chord(chroma_mean)
        detected_chords.append(chord)
    # Display detected chords
    chords_str = ', '.join(detected_chords)
    messagebox.showinfo("Detected Chords", f"Chords detected:\n{chords_str}")
    # Optionally, plot the chroma
    plt.figure(figsize=(10, 4))
    librosa.display.specshow(chroma, x_axis='time', y_axis='chroma', hop_length=hop_length, cmap='coolwarm')
    plt.title('Chroma Feature')
    plt.colorbar()
    plt.tight_layout()
    plt.show()


def identify_chord(chroma_vector):
    """Identify the chord from the chroma vector."""
    # Define chord templates
    chord_templates = {
        'C': [1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0],
        'C#': [0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0],
        'D': [0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0],
        'D#': [0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0],
        'E': [0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1],
        'F': [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0],
        'F#': [0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1],
        'G': [1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0],
        'G#': [0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0],
        'A': [0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0],
        'A#': [0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0],
        'B': [0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1]
    }
    # Compare chroma_vector with each chord template using cosine similarity
    max_similarity = -1  # Initialize with -1 to ensure any positive similarity is higher
    detected_chord = "Unknown"
    for chord, template in chord_templates.items():
        template_np = np.array(template)
        if np.linalg.norm(template_np) == 0 or np.linalg.norm(chroma_vector) == 0:
            similarity = 0
        else:
            similarity = np.dot(chroma_vector, template_np) / (np.linalg.norm(chroma_vector) * np.linalg.norm(template_np))
        if similarity > max_similarity:
            max_similarity = similarity
            detected_chord = chord
    return detected_chord


def browse_file():
    """Open a file dialog to select an audio file for chord detection."""
    file_path = filedialog.askopenfilename(filetypes=[("WAV files", "*.wav")],
                                           title="Select Audio File for Chord Detection")
    if file_path:
        detect_chords(file_path)


def play_selected_audio():
    """Play the selected audio file."""
    file_path = filedialog.askopenfilename(filetypes=[("WAV files", "*.wav")],
                                           title="Select Audio File to Play")
    if file_path:
        threading.Thread(target=play_audio, args=(file_path,), daemon=True).start()


def main():
    """Create the GUI."""
    root = tk.Tk()
    root.title("Chord Generator and Detector")

    # Set the window size and disable resizing for consistency
    root.geometry("450x350")
    root.resizable(False, False)

    # Create Generate button
    generate_button = tk.Button(root, text="Generate Audio", command=save_audio, width=25,
                                height=2, bg='lightblue')
    generate_button.pack(pady=15)

    # Create Play button
    play_button = tk.Button(root, text="Play Audio", command=play_selected_audio, width=25,
                            height=2, bg='lightgreen')
    play_button.pack(pady=10)

    # Create Detect button
    detect_button = tk.Button(root, text="Detect Chords", command=browse_file, width=25,
                              height=2, bg='lightyellow')
    detect_button.pack(pady=15)

    # Instructions Label
    instructions = tk.Label(root,
                            text="Instructions:\n1. Click 'Generate Audio' to create a chord progression.\n"
                                 "2. Save the audio as WAV.\n"
                                 "3. Use 'Play Audio' to listen to the saved file.\n"
                                 "4. Use 'Detect Chords' to analyze any WAV file.",
                            justify="left")
    instructions.pack(padx=10, pady=10)

    root.mainloop()


if __name__ == "__main__":
    # Check if FFmpeg is installed (necessary only if working with MP3s)
    # Since we're using WAV, FFmpeg is not required for playback
    # However, pydub may still require it for exporting MP3s
    # Commenting out the FFmpeg check
    # if not shutil.which("ffmpeg"):
    #     messagebox.showerror("FFmpeg Not Found",
    #                          "FFmpeg is not installed or not found in system PATH.\n"
    #                          "Please install FFmpeg to enable MP3 export.")
    #     sys.exit(1)
    main()