Have this code but the simplefyed button who should use better techneques works but the teqneque semms not tobe so great also the next cord is not working correctly looks like it totaly seperate from the currentd cord display. fix all these and other issues and print the complete code and only the code in one file

The simplefied chords are in the beginning switching around as afast as the non simplefied code and then get Stuck in A so there is something vwery wrong with the simplification calculation. Simplefying should mean to combine the predictions to get a good prediction of the cords. Remember the cords normaly switch slowly and stay the same for about two seconds. the simplefyed cords should look at thesese 2 second windows and only display them 2 second is rough  estimate tdepends on the sound so dont use the 2 second as a must time limit but ats a rough guide

import sys
import time
import numpy as np
import librosa
from PyQt6.QtWidgets import (
    QApplication, QMainWindow, QPushButton, QFileDialog,
    QLabel, QVBoxLayout, QHBoxLayout, QWidget, QSlider
)
from PyQt6.QtCore import Qt, QTimer
import pygame
from mutagen.mp3 import MP3
import os
import bisect

# Define chord templates (Major, Minor, and Extended Chords)
CHORD_TEMPLATES = {
    'C': [1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1],
    'C#': [1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0],
    'Cm': [1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1],
    'Cmaj7': [1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1],
    'D': [0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0],
    'D#': [0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0],
    'Dm': [0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0],
    'Dmaj7': [0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0],
    'E': [0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1],
    'Em': [0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1],
    'F': [1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0],
    'F#': [0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0],
    'Fm': [1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1],
    'Fmaj7': [1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0],
    'G': [0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0],
    'G#': [0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0],
    'Gm': [0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0],
    'Gmaj7': [0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0],
    'A': [0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0],
    'A#': [0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1],
    'Am': [0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0],
    'Amaj7': [0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0],
    'B': [0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0],
    'Bm': [0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0],
}

# Normalize chord templates
for chord in CHORD_TEMPLATES:
    template = np.array(CHORD_TEMPLATES[chord], dtype=float)
    CHORD_TEMPLATES[chord] = template / np.linalg.norm(template) if np.linalg.norm(template) != 0 else template


class ChordPlayer(QMainWindow):
    def __init__(self):
        super().__init__()
        pygame.mixer.init()
        self.current_chord_index = 0
        self.is_playing = False
        self.start_time = 0
        self.song_length = 0
        self.current_position = 0
        self.current_file = None
        self.slider_being_dragged = False

        self.chord_data = []  # List of {'time': float, 'chord': str}
        self.lyrics_data = []  # Placeholder for lyrics data

        # Parameters for Chord Stability
        self.stability_threshold = 3
        self.stability_counter = 0
        self.last_detected_chord = None

        # Simplified Chord Progression
        self.simplified_chords = []
        self.use_simplified = False

        self.initUI()

    def initUI(self):
        self.setWindowTitle('Chord Player')
        self.setGeometry(100, 100, 800, 600)

        central_widget = QWidget()
        self.setCentralWidget(central_widget)
        layout = QVBoxLayout(central_widget)

        self.song_label = QLabel('No song loaded')
        self.song_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
        layout.addWidget(self.song_label)

        self.transposition = 0
        self.transposition_label = QLabel('Transposition: +0')
        self.transposition_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
        layout.addWidget(self.transposition_label)

        self.chord_label = QLabel('No Chord')
        self.chord_label.setStyleSheet('''
            font-size: 48px;
            font-weight: bold;
            background-color: white;
            border: 2px solid black;
            border-radius: 10px;
            padding: 10px;
            margin: 10px;
            min-height: 100px;
        ''')
        self.chord_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
        layout.addWidget(self.chord_label)

        self.next_chord_label = QLabel('Next: -')
        self.next_chord_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
        self.next_chord_label.setStyleSheet('font-size: 18px; color: gray;')
        layout.addWidget(self.next_chord_label)

        time_layout = QHBoxLayout()
        self.current_time_label = QLabel('0:00')
        self.total_time_label = QLabel('/ 0:00')
        time_layout.addWidget(self.current_time_label)
        time_layout.addWidget(self.total_time_label)
        layout.addLayout(time_layout)

        self.time_slider = QSlider(Qt.Orientation.Horizontal)
        self.time_slider.setEnabled(False)
        self.time_slider.sliderPressed.connect(self.slider_pressed)
        self.time_slider.sliderReleased.connect(self.slider_released)
        self.time_slider.valueChanged.connect(self.slider_value_changed)
        self.time_slider.setMaximum(1000)
        layout.addWidget(self.time_slider)

        button_layout = QHBoxLayout()

        self.load_mp3_btn = QPushButton('Load Song')
        self.load_mp3_btn.clicked.connect(self.load_mp3)
        button_layout.addWidget(self.load_mp3_btn)

        self.prev_btn = QPushButton('Previous Chord')
        self.prev_btn.clicked.connect(self.previous_chord)
        button_layout.addWidget(self.prev_btn)

        self.play_btn = QPushButton('Play')
        self.play_btn.clicked.connect(self.toggle_playback)
        button_layout.addWidget(self.play_btn)

        self.next_btn = QPushButton('Next Chord')
        self.next_btn.clicked.connect(self.next_chord)
        button_layout.addWidget(self.next_btn)

        self.transpose_down = QPushButton('♭')
        self.transpose_down.clicked.connect(lambda: self.transpose(-1))
        button_layout.addWidget(self.transpose_down)

        self.transpose_up = QPushButton('♯')
        self.transpose_up.clicked.connect(lambda: self.transpose(1))
        button_layout.addWidget(self.transpose_up)

        self.toggle_btn = QPushButton('Show Simplified Chords')
        self.toggle_btn.clicked.connect(self.toggle_chord_display)
        button_layout.addWidget(self.toggle_btn)

        layout.addLayout(button_layout)

        self.debug_label = QLabel('')
        self.debug_label.setStyleSheet('color: gray; font-size: 10px;')
        layout.addWidget(self.debug_label)

        self.lyrics_label = QLabel('Lyrics will appear here.')
        self.lyrics_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
        self.lyrics_label.setStyleSheet('''
            font-size: 18px;
            color: blue;
            background-color: lightyellow;
            border: 1px solid black;
            padding: 10px;
            margin: 10px;
            min-height: 150px;
        ''')
        layout.addWidget(self.lyrics_label)

        self.timer = QTimer()
        self.timer.timeout.connect(self.update_display)
        self.timer.start(200)

    def format_time(self, seconds):
        minutes = int(seconds // 60)
        seconds = int(seconds % 60)
        return f"{minutes}:{seconds:02d}"

    def load_mp3(self):
        filename, _ = QFileDialog.getOpenFileName(self, "Load MP3", "", "MP3 Files (*.mp3)")
        if filename:
            try:
                self.current_file = filename
                pygame.mixer.music.load(filename)
                audio = MP3(filename)
                self.song_length = audio.info.length
                self.total_time_label.setText(f"/ {self.format_time(self.song_length)}")
                self.time_slider.setEnabled(True)
                self.song_label.setText(f"Now Playing: {os.path.basename(filename)}")
                self.current_position = 0
                self.current_chord_index = 0
                self.update_time_display()
                self.detect_chords(filename)
                self.update_chord_display()

                self.simplified_chords = self.simplify_chord_progression()

            except Exception as e:
                self.song_label.setText(f"Error loading MP3: {str(e)}")
                print(f"Error loading MP3: {str(e)}")

    def detect_chords(self, filename):
        y, sr = librosa.load(filename, sr=None)
        chroma = librosa.feature.chroma_cqt(y=y, sr=sr)
        self.chord_data = self.map_chroma_to_chords(chroma, sr)
        self.prepare_chord_times()

    def map_chroma_to_chords(self, chroma, sr):
        chord_labels = list(CHORD_TEMPLATES.keys())
        chords = []
        chord_times = []

        window_size = 5
        for i in range(0, chroma.shape[1], window_size):
            window_chroma = chroma[:, i:i + window_size]
            if window_chroma.size == 0:
                continue
            mean_chroma = np.mean(window_chroma, axis=1)
            if np.linalg.norm(mean_chroma) == 0:
                continue
            mean_chroma /= np.linalg.norm(mean_chroma)
            similarity = {}
            for chord in chord_labels:
                similarity[chord] = np.dot(mean_chroma, CHORD_TEMPLATES[chord])
            detected_chord = max(similarity, key=similarity.get)
            time_stamp = i * (512 / sr)
            chords.append({"time": time_stamp, "chord": detected_chord})
            chord_times.append(time_stamp)
        return chords

    def prepare_chord_times(self):
        self.chord_times = [chord['time'] for chord in self.chord_data]

    def find_current_chord(self):
        current_time = self.current_position
        if not self.chord_data:
            return
        index = bisect.bisect_right(self.chord_times, current_time) - 1
        if index < 0:
            index = 0

        detected_chord = self.chord_data[index]['chord']

        if detected_chord != self.last_detected_chord:
            self.stability_counter += 1
        else:
            self.stability_counter = 1

        self.last_detected_chord = detected_chord

        if self.stability_counter >= self.stability_threshold:
            if index != self.current_chord_index:
                self.current_chord_index = index
                self.update_chord_display()
                self.stability_counter = 0

    def update_chord_display(self):
        if self.use_simplified and self.simplified_chords:
            if 0 <= self.current_chord_index < len(self.simplified_chords):
                current_chord = self.simplified_chords[self.current_chord_index]['chord']
                self.chord_label.setText(current_chord)

                if self.current_chord_index + 1 < len(self.simplified_chords):
                    next_chord = self.simplified_chords[self.current_chord_index + 1]['chord']
                    self.next_chord_label.setText(f"Next: {next_chord}")
                else:
                    self.next_chord_label.setText("Next: -")
        else:
            if 0 <= self.current_chord_index < len(self.chord_data):
                current_chord = self.chord_data[self.current_chord_index]['chord']
                self.chord_label.setText(current_chord)

                if self.current_chord_index + 1 < len(self.chord_data):
                    next_chord = self.chord_data[self.current_chord_index + 1]['chord']
                    self.next_chord_label.setText(f"Next: {next_chord}")
                else:
                    self.next_chord_label.setText("Next: -")
            else:
                self.chord_label.setText("No Chord")
                self.next_chord_label.setText("Next: -")

    def update_display(self):
        if self.is_playing:
            self.current_position = time.time() - self.start_time

            if not self.slider_being_dragged and self.song_length > 0:
                slider_value = int((self.current_position / self.song_length) * 1000)
                self.time_slider.setValue(slider_value)

            self.update_time_display()
            self.find_current_chord()

    def update_time_display(self):
        self.current_time_label.setText(self.format_time(self.current_position))

    def slider_pressed(self):
        self.slider_being_dragged = True

    def slider_released(self):
        self.slider_being_dragged = False
        value = self.time_slider.value()
        position = (value / 1000.0) * self.song_length
        pygame.mixer.music.set_pos(position)
        self.current_position = position
        self.start_time = time.time() - position
        self.find_current_chord()

    def slider_value_changed(self, value):
        if self.slider_being_dragged:
            position = (value / 1000.0) * self.song_length
            self.current_time_label.setText(self.format_time(position))

    def toggle_playback(self):
        if not self.is_playing:
            try:
                pygame.mixer.music.play(start=self.current_position)
                self.start_time = time.time() - self.current_position
                self.is_playing = True
                self.play_btn.setText('Pause')
            except Exception as e:
                self.song_label.setText(f"Error playing song: {str(e)}")
                print(f"Error playing song: {str(e)}")
        else:
            pygame.mixer.music.pause()
            self.is_playing = False
            self.play_btn.setText('Play')
            self.current_position = time.time() - self.start_time

    def next_chord(self):
        if self.use_simplified and self.simplified_chords:
            if self.current_chord_index < len(self.simplified_chords) - 1:
                self.current_chord_index += 1
                next_time = float(self.simplified_chords[self.current_chord_index]["time"])
                self.current_position = next_time
                if self.is_playing:
                    pygame.mixer.music.set_pos(next_time)
                self.start_time = time.time() - next_time
                self.update_chord_display()
        else:
            if self.current_chord_index < len(self.chord_data) - 1:
                self.current_chord_index += 1
                next_time = float(self.chord_data[self.current_chord_index]["time"])
                self.current_position = next_time
                if self.is_playing:
                    pygame.mixer.music.set_pos(next_time)
                self.start_time = time.time() - next_time
                self.update_chord_display()

    def previous_chord(self):
        if self.use_simplified and self.simplified_chords:
            if self.current_chord_index > 0:
                self.current_chord_index -= 1
                prev_time = float(self.simplified_chords[self.current_chord_index]["time"])
                self.current_position = prev_time
                if self.is_playing:
                    pygame.mixer.music.set_pos(prev_time)
                self.start_time = time.time() - prev_time
                self.update_chord_display()
        else:
            if self.current_chord_index > 0:
                self.current_chord_index -= 1
                prev_time = float(self.chord_data[self.current_chord_index]["time"])
                self.current_position = prev_time
                if self.is_playing:
                    pygame.mixer.music.set_pos(prev_time)
                self.start_time = time.time() - prev_time
                self.update_chord_display()

    def transpose(self, semitones):
        self.transposition += semitones
        self.transposition_label.setText(f"Transposition: {self.transposition:+d}")

        for chord_item in self.chord_data:
            original_chord = chord_item['chord']
            transposed = self.transpose_chord(original_chord, semitones)
            chord_item['chord'] = transposed

        if self.use_simplified and self.simplified_chords:
            for chord_item in self.simplified_chords:
                original_chord = chord_item['chord']
                transposed = self.transpose_chord(original_chord, semitones)
                chord_item['chord'] = transposed

        self.update_chord_display()

    def transpose_chord(self, chord, semitones):
        base_chords = ['C', 'C#', 'D', 'D#', 'E', 'F',
                       'F#', 'G', 'G#', 'A', 'A#', 'B']

        is_minor = False
        is_major7 = False
        chord_root = chord
        chord_suffix = ''

        if chord.endswith('m'):
            is_minor = True
            chord_root = chord[:-1]
        elif chord.endswith('maj7'):
            is_major7 = True
            chord_root = chord[:-4]

        if chord_root not in base_chords:
            return chord

        idx = base_chords.index(chord_root)
        transposed_idx = (idx + semitones) % 12
        transposed_root = base_chords[transposed_idx]

        if is_minor:
            chord_suffix = 'm'
        elif is_major7:
            chord_suffix = 'maj7'

        transposed_chord = transposed_root + chord_suffix
        return transposed_chord

    def toggle_chord_display(self):
        self.use_simplified = not self.use_simplified
        if self.use_simplified:
            self.toggle_btn.setText('Show Detailed Chords')
            self.chord_label.setText('No Chord')
            self.current_chord_index = 0
            self.update_chord_display()
        else:
            self.toggle_btn.setText('Show Simplified Chords')
            self.chord_label.setText('No Chord')
            self.current_chord_index = 0
            self.update_chord_display()

    def simplify_chord_progression(self):
        from collections import Counter

        if not self.chord_data:
            return []

        chord_sequence = [chord['chord'] for chord in self.chord_data]
        chord_counts = Counter(chord_sequence)
        top_n = 5
        common_chords = [chord for chord, count in chord_counts.most_common(top_n)]

        simplified_chords = []
        for chord_item in self.chord_data:
            chord = chord_item['chord']
            if chord in common_chords:
                simplified_chords.append(chord_item)
            else:
                if simplified_chords:
                    simplified_chords.append({"time": chord_item['time'], "chord": simplified_chords[-1]['chord']})
                else:
                    simplified_chords.append({"time": chord_item['time'], "chord": common_chords[0]})

        final_simplified = []
        for chord in simplified_chords:
            if not final_simplified or chord['chord'] != final_simplified[-1]['chord']:
                final_simplified.append(chord)

        return final_simplified


def main():
    app = QApplication(sys.argv)
    player = ChordPlayer()
    player.show()
    sys.exit(app.exec())


if __name__ == '__main__':
    main()