write this code in plain English on what is happening and define all technical jargon and words of what you mean in brackets for me to connect the dots and understand : import numpy as np
from collections import defaultdict


# Define a mapping from phonemes to visemes
phoneme_to_viseme = {
    'AA': 'V1', 'AE': 'V1', 'AH': 'V1', 'AO': 'V1', 'AW': 'V2', 'AY': 'V2',
    'B': 'V3', 'P': 'V3', 'M': 'V3',
    'CH': 'V4', 'JH': 'V4', 'SH': 'V4', 'ZH': 'V4',
    'D': 'V5', 'T': 'V5',
    'EH': 'V6', 'EY': 'V6', 'IH': 'V6', 'IY': 'V6',
    'F': 'V7', 'V': 'V7',
    'G': 'V8', 'K': 'V8', 'NG': 'V8',
    'L': 'V9', 'R': 'V9',
    'N': 'V10', 'S': 'V10', 'Z': 'V10',
    'OW': 'V11', 'UH': 'V11', 'UW': 'V11',
    'TH': 'V12', 'DH': 'V12',
    'HH': 'V13',
    'ER': 'V14',
    # Add any additional phonemes as needed
}


class Phoneme:
    def __init__(self, name, start_time, end_time):  # The init method is crucial 
        self.name = name                      # Phoneme label (e.g., 'AH', 'T', 'B', etc.)
        self.start_time = start_time          # Start time of the phoneme in the audio/video
        self.end_time = end_time              # End time of the phoneme in the audio/video
        self.viseme = phoneme_to_viseme.get(name, 'UNK')  # Corresponding viseme; 'UNK' if not found


class PhonemeSequence:
    def __init__(self, phonemes):
        self.phonemes = phonemes  # A list of Phoneme objects

    def length(self):
        return len(self.phonemes)

    def viseme_sequence(self):
        return [phoneme.viseme for phoneme in self.phonemes]


def build_viseme_bigram_index(repository_sequences):
    index = defaultdict(list)
    for seq_id, seq in enumerate(repository_sequences):
        visemes = seq.viseme_sequence()
        for i in range(len(visemes) - 1):
            bigram = (visemes[i], visemes[i + 1])
            index[bigram].append((seq_id, i))
    return index


# Hypothetical repository sequences (you would replace these with actual data)
repository_sequences = [
    PhonemeSequence([
        Phoneme('B', 0.0, 0.1),
        Phoneme('AE', 0.1, 0.2),
        Phoneme('T', 0.2, 0.3)
    ]),
    PhonemeSequence([
        Phoneme('D', 0.0, 0.1),
        Phoneme('AH', 0.1, 0.2),
        Phoneme('G', 0.2, 0.3)
    ]),
    PhonemeSequence([
        Phoneme('B', 0.0, 0.1),
        Phoneme('AE', 0.1, 0.2),
        Phoneme('D', 0.2, 0.3),
        Phoneme('ER', 0.3, 0.4)
    ]),
    # Add more sequences as needed
]

# Build the index
viseme_bigram_index = build_viseme_bigram_index(repository_sequences)

# Print the index to see its structure
print("\nViseme Bigram Index:")
for bigram, locations in viseme_bigram_index.items():
    print(f"{bigram}: {locations}")


def substitution_cost(p1, p2):
    return 0 if p1.viseme == p2.viseme else 1  # Cost is 0 if visemes match, else 1


def fast_phoneme_search(edit_seq, repository_sequences, index, max_subseq_length=6):
    matched_sequences = []
    i = 0  # Start index in the edit sequence
    while i < edit_seq.length():
        max_len = min(max_subseq_length, edit_seq.length() - i)
        found_match = False
        # Try to find the longest possible match
        for l in range(max_len, 1, -1):
            subseq = PhonemeSequence(edit_seq.phonemes[i:i + l])
            visemes = subseq.viseme_sequence()
            if len(visemes) < 2:
                continue  # Need at least two visemes for a bigram
            bigram = (visemes[0], visemes[1])
            candidates = index.get(bigram, [])
            for seq_id, pos in candidates:
                repo_seq = repository_sequences[seq_id]
                if pos + l <= repo_seq.length():
                    repo_subseq = PhonemeSequence(repo_seq.phonemes[pos:pos + l])
                    # Compute total substitution cost
                    total_cost = sum(
                        substitution_cost(p1, p2)
                        for p1, p2 in zip(subseq.phonemes, repo_subseq.phonemes)
                    )
                    if total_cost == 0:
                        # Found an exact viseme match
                        matched_sequences.append((repo_subseq, i, i + l))
                        i += l  # Advance index
                        found_match = True
                        break
            if found_match:
                break
        if not found_match:
            # No match found, advance by one phoneme
            i += 1
    return matched_sequences


if __name__ == "__main__":
    # Example phonemes for a word (e.g., "BATER")
    phoneme_list = [
        Phoneme('B', 0.0, 0.1),
        Phoneme('AE', 0.1, 0.2),
        Phoneme('D', 0.2, 0.3),
        Phoneme('ER', 0.3, 0.4)  # Added 'ER' for a more comprehensive example
    ]

    # Create a PhonemeSequence for initial example (optional)
    sequence = PhonemeSequence(phoneme_list)

    # Get the length of the sequence
    print("Sequence Length:", sequence.length())

    # Get the viseme sequence
    print("Viseme Sequence:", sequence.viseme_sequence())
    
    # Define edit_sequence (could be different; here using the same for simplicity)
    edit_sequence = PhonemeSequence([
        Phoneme('B', 0.0, 0.1),
        Phoneme('AE', 0.1, 0.2),
        Phoneme('D', 0.2, 0.3),
        Phoneme('ER', 0.3, 0.4)  # Same as phoneme_list for this example
    ])
    
    # Perform fast phoneme search
    matched_sequences = fast_phoneme_search(edit_sequence, repository_sequences, viseme_bigram_index)
    
    # Print matched sequences
    print("\nMatched Sequences:")
    for repo_subseq, start_idx, end_idx in matched_sequences:
        visemes = repo_subseq.viseme_sequence()
        print(f"Edit positions {start_idx}-{end_idx}: Repository Sequence Visemes: {visemes}")
    
    # Define placeholder functions
    FRAME_RATE = 30  # Frames per second (adjust if needed)
    
    def get_expression_parameters(phoneme_sequence):
        # Placeholder: Generate random parameters
        duration = phoneme_sequence.phonemes[-1].end_time - phoneme_sequence.phonemes[0].start_time
        num_frames = int(duration * FRAME_RATE)
        # Assume 64 expression parameters per frame
        return np.random.rand(num_frames, 64)
    
    def retime_parameters(parameters, target_num_frames):
        # Retime parameters to match the target number of frames
        num_frames = parameters.shape[0]
        retimed_params = np.zeros((target_num_frames, parameters.shape[1]))
        for i in range(parameters.shape[1]):
            retimed_params[:, i] = np.interp(
                np.linspace(0, num_frames - 1, target_num_frames),
                np.arange(num_frames),
                parameters[:, i]
            )
        return retimed_params
    
    def stitch_sequences(matched_sequences, edit_sequence):
        stitched_parameters = []
        for repo_subseq, start_idx, end_idx in matched_sequences:
            # Get expression parameters for the repository subsequence
            source_params = get_expression_parameters(repo_subseq)
            # Calculate the target duration from the edit sequence
            start_time = edit_sequence.phonemes[start_idx].start_time
            end_time = edit_sequence.phonemes[end_idx - 1].end_time
            target_duration = end_time - start_time
            target_num_frames = int(target_duration * FRAME_RATE)
            # Retime source parameters to match the target number of frames
            retimed_params = retime_parameters(source_params, target_num_frames)
            # Append to the stitched parameters
            stitched_parameters.append(retimed_params)
        # Concatenate all parameters into one sequence
        if stitched_parameters:
            stitched_parameters = np.vstack(stitched_parameters)
        else:
            stitched_parameters = np.array([])  # Empty array if no matches
        return stitched_parameters
    
    # Stitch the matched sequences to get source expression parameters
    stitched_parameters = stitch_sequences(matched_sequences, edit_sequence)
    
    # Print the shape of the stitched parameters
    print("\nStitched Parameters Shape:", stitched_parameters.shape)