# Import systémovej funkcie wait
from os import wait
# Import knižnice PYO pre generovanie a manipulácizu s  audio signálmi
from pyo import *
# Import knižnice OpenCV na manipuláciu s videami
import cv2 as cv2
# Import knižnice numpy pre matematické operácie
import numpy as np
# Import Savgolského filtru z knižnice Scipy
from scipy.signal import savgol_filter


#   Trieda SynthPlayer - trieda ktorá vytvára objekt Syntetizátora
class SynthPlayer:
    #   Inicializácia triedy, nastavenie základných hodnôt premenných
    def __init__(self, transpo=1, hfdamp=5000, lfofreq=0.2, mul=1, f1=1000, q1=1):
        #   Nastavenie počtu ekvalizačných filtrov
        number_of_filters = 5
        polynumber = 5
        #   Základné nastavenie ovládania pomocou MIDI kontroleru
        self.transpo = Sig(transpo)
        self.note = Notein(poly=polynumber, scale=1, first=0, last=127)
        #   Vytvorenie poľa pre hodnoty fc jednotlivých filtrov
        self.eqfreqs = np.array([])
        #   vyrátanie konštanty pre prepočet frekvencií - prvý filter bude mat pevne fc = 60Hz a posledný fc = 20kHz
        k = (20000/60)**(1/(number_of_filters-1))
        #   Výpočet logaritmicky vzdialených hodnôt fc podľa počtu filtrov
        for i in range(number_of_filters):
            self.eqfreqs = np.append(self.eqfreqs, int(round(60*(k**(i)))))
        self.eqfreqs = self.eqfreqs.tolist()

        #   Nastavenie premennej control_selection - ovládanie generovanie zvukovej stopy alebo vstupu z MIDI kontroléru
        self.control_selection = True
        # Rozdelenie vstupu syntetizátora podľa voľby kontroly
        # Nastavenie pre MIDI kontroler
        if self.control_selection == False:
            self.pit = self.note["pitch"] * self.transpo
            self.amp = MidiAdsr(
                self.note["velocity"], attack=0.001, decay=0.1, sustain=0.4, release=0.5, mul=0.1)
        else:
            # Nastavenie pre generovaný zvuk
            self.pit = 200
            self.amp = Adsr(decay=0.1)
        #   Vytvorenie oscilátorov
        for x in range(polynumber):
            exec(f'self.osc{x} = Blit(freq=self.pit, harms=1, mul=self.amp)')

        #   Spojenie oscilátorov pomocou funkcie PYO Mix()
        self.eq0 = Mix([self.osc0, self.osc1, self.osc2,
                       self.osc3, self.osc4], voices=2)

        #   Vytvorenie ekvalizačných filtrov
        for x in range(number_of_filters):
            exec(
                f'self.eq{x+1} = EQ(input=self.eq{x}, freq = self.eqfreqs[{x}], q=1, boost=0, type=0, mul=1)')

        #   Vytvorenie výstupných filtrov High Pass a Low Pass
        self.LC = Biquad(self.eq4, freq=50, q=1, type=1, mul=1, add=0)
        self.output = Biquad(self.LC, freq=20000, q=1, type=0, mul=0.1, add=0)

        #   Vytvorenie poľa pre pamäť frekvencií
        self.prev_freq = [0, 0, 0, 0, 0]

    @staticmethod
    def filtering(self, boost, Q, intensity):
        #   Funkcia filtering získava ako vstupné hodnoty boost - zosílenie filtra, Q - jakost filtra a intensity - hodnota intenzity filtrácie
        #   Nastavenie jednotlivých filtrov
        for x in range(len(boost)):
            exec(f'self.eq{x+1}.q = Q[x]*10')
            exec(f'self.eq{x+1}.boost = boost[x]*intensity')
 
    @staticmethod
    def caculate_pitch(self, pitch_array, octave, volnumber, saturation):
        #   Funkcia calculate_pitch získava vstupné hodnoty pitch_array - nastavenie frekvencií oscilátorov, octave - nastavenie oktáv oscilátorov,
        #   volnumber - hodnota intenzity oscilátora, saturation - počet harmonických frekvencií oscilátora
        #   Zmena frekvencie ak je vstupná frekvencia iná, než predošlá
        for x in range(len(pitch_array)):
            if self.prev_freq[x] != pitch_array[x]:
                exec(f'self.osc{x}.harms = int(saturation[x])')
                exec(f'self.osc{x}.freq = pitch_array[x]*octave[x]')
                exec(f'self.osc{x}.mul = volnumber')
            else:
                pass
        #   uloženie frekvencií pre spracovanie v ďaľsom koroku
        self.prev_freq = pitch_array

    @staticmethod
    def masterfader_control(self, masterfader_value):
        #   Nastavenie výstupnej intenzity - "Masterfader"
        self.output.mul = float(np.log10(masterfader_value/10))
        print(masterfader_value)
        print(self.output.mul)
    def out(self):
        #   Výstup syntetizátora
        self.output.out()
        return self

    def sig(self):
        #   Vrátenie signálu
        return self.output


def get_smooth_normalized_curve(histogram):
    #   Funkcia pre výpočet normalizovanej, vyhladenej krivky
    #   Výpočet maximálnej hodnoty histogramu
    mx = max(histogram)
    n = np.zeros(len(histogram))
    #   Normalizácia hodnôt histogramu
    for x in range(len(histogram)):
        n[x] = histogram[x]/mx
    #   Vyhladzovanie savgolského filtrom
    n_smooth = savgol_filter(n, 11, 2)
    return n_smooth


def create_coeff(graf):
    # Funkcia pre výpočet hodnôt pre ekvalizačné filtre
    number_of_bands = 5
    split = np.array_split(graf, number_of_bands)
    i = 0
    Sum = np.zeros(number_of_bands)
    peak = np.zeros(number_of_bands)
    cf = np.zeros(number_of_bands)
    while i < number_of_bands:
        #   Výpočet priemernej hodnoty Sum v jednotlivých pásmach
        Sum[i] = sum(split[i])/len(split[i])-0.5
        if(Sum[i]) >= 0:
            #   Výpočet maximálnej/minimálnej hodnoty peak
            peak[i] = max(split[i])
        else:
            peak[i] = min(split[i])
        #   Výpočet crest-factora, ktorý následne využívame pri nastavení Q
        cf[i] = abs(peak[i]/Sum[i]*10)
        #   Nastavenie hraníc pre PYO hodnotu q
        if cf[i] < 1:
            cf[i] = 1
        if cf[i] > 500:
            cf[i] = 500
        i = i+1
    #   Vrátenie poľa s hodnotami peak a cf
    return [peak, cf]


def pitch_extraction(image, k, a1, tonality, mod_control):
    #   Funkcia pre výpočet hodnôt frekvencií oscilátorov

    #   Zmena priestoru obrázka na HSV
    main_image_HSV = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
    #   Výpočet priemerného HSV celkového obrázka
    avg_color_per_row_main = np.average(main_image_HSV, axis=0)
    current_avg_HSV_main = np.average(avg_color_per_row_main, axis=0)

    #   Nastavenie maximálneho počtu harmonických zložiek oscilátora
    max_harmonics = 20

    #   Zistenie tonálneho centra generovaného zvuku
    #   Premenná mod_control je nastaviteľná z GUI a ovláda moduláciu signálu; bez modulácie vychádza z a = 440hz
    if mod_control == True:
        scale = int(np.floor(current_avg_HSV_main[0]/180*12))
    if mod_control == False:
        scale = 1

    #   Zistenie dimenzií obrázka a následné rozdelenie na podobrázky do poľa crop
    height, width = image.shape[:2]
    crop = []
    for x in range(k):
        start_r, start_c, stop_r, stop_c = int(0), int(
            x*width/k), int(height), int(width/k + x*width/k)
        crop.append(image[start_r:stop_r, start_c:stop_c])

    #   Vytvorenie prázdnych premenných pre výpočet priemernej HSV jednotlivých obrázkov
    crop_hsv = []
    avgHSV = []
    #   Vytvorenie rozmedzia pre hodnotu H (OpenCV používa mierku H: 0-180)
    Hnum = [x for x in range(0, 180)]

    #   Zistenie nastavenia tonality - ovládanie z GUI
    #   Ak je tonalita FALSE, generovaný zvuk bude využívať chromatickú stupnicu
    #   Ak je tonalita TRUE, generovaný zvuk bude využívať durovú stupnicu

    if tonality == False:
        tones = 12
        multiples = [x for x in range(12)]

    if tonality == True:
        tones = 7
        multiples = [1, 3, 5, 6, 8, 10, 12]

    #   Rozdelenie poľa s hdnotami Hnum podľa prislúchajúcich tónov
    Hsplit = np.array_split(Hnum, tones)

    #   Vytvorenie rozmedzia pre hodnoty V (OpenCV používa mierku V: 0-255)
    Vnum = [x for x in range(0, 255)]
    #   Rozdelenie Vnum na menšie polia pre 8 oktáv
    Vsplit = np.array_split(Vnum, 8)
    #   Premenná temp_k je koeficient temperovaného ladenia
    temp_k = 2**(1/12)
    #   nastavenie prázdneho poľa notes, kde sa budú ukladať výsledné tóny a definícia koeficientov "oktavy" jednotlivých oktáv
    notes = []
    oktavy = [1/16, 1/8, 1/4, 1/2, 1, 2, 4, 8]

    #   Vytvorenie hodnôt frekvencií jednotlivých tónov stupnice
    for x in range(tones):
        notes.append(440*(temp_k**scale)*(temp_k**multiples[x]))

    #   Nastavenie prádznych polí do ktorých sa budu naplňovať požadované parametre
    anote = []
    volnumber = []
    octave = []
    saturation = []

    #   Prechádzanie každého obrázka
    for x in range(len(crop)):
        #   Premena na HSV a následný výpočet priemernej HSV
        crop_hsv.append(cv2.cvtColor(crop[x], cv2.COLOR_BGR2HSV))
        avg_color_per_row = np.average(crop_hsv[x], axis=0)
        current_avg_HSV = np.average(avg_color_per_row, axis=0)
        avgHSV.append(current_avg_HSV)
        #   Vytvorenie celočíselných hodnôt HSV
        H_number = int(current_avg_HSV[0])
        V_number = int(current_avg_HSV[2])
        S_number = current_avg_HSV[1]
        #   Prechádzanie každého tónu
        for x in range(tones):
            #   Priradenie tónu, ak je nájdený v rozsahu
            if H_number in Hsplit[x]:
                anote.append(notes[x])
        #   Prechádzanie oktáv
        for x in range(8):
            #   Priradenie oktávy ak je nájdená v rozsahu
            if V_number in Vsplit[x]:
                octave.append(oktavy[x])
        #   Priradenie saturation a volnumber zo Saturácie, normalizačný prepočet, prepočet na logaritmickú mierku
        saturation.append(S_number/255*max_harmonics)
        volnumber.append(np.log10(S_number)/np.log10(255))
    #   Volanie funkcie calculate_pitch Syntetizátora s vypočítanými vstupnými hodnotami
    a1.caculate_pitch(a1, anote, octave, volnumber, saturation)


def M1comp(frame, a1, bins, masterfader_value, tonality, intensity, mod):
    #   Funkcia volaná v hlavnom threade aplikácie, spúšťa analýzu videa a generovanie audio signálu syntetizátorom
    #   Zistenie počtu pixelov v obrázku
    #n_o_pixels = np.prod(frame.shape[:2])
    #   Vytvorenie šedotónového obrázku z hlavného obrázku
    gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
    #   Výpočet histogramu šedotónového obrázku
    histogram = cv2.calcHist([gray], [0], None, [bins], [0, 255]) #/ n_o_pixels
    #   Volanie funkcie na normalizáciu a vyhladenie
    smooth = get_smooth_normalized_curve(histogram)
    #   Volanie funkcie na výpočet parametrov ekvalizačných filtrov, premena typu na python list
    getCoeff = create_coeff(smooth)
    boost = getCoeff[0].tolist()
    q = getCoeff[1].tolist()
    #   Volanie funkcie na výpočet výšky tónov generovaných oscilátormi
    pitch_extraction(frame, 5, a1, tonality, mod)
    #   Volanie funkcií Syntetizátora na filtráciu a kontrolu master fadera
    a1.filtering(a1, boost, q, intensity)
    a1.masterfader_control(a1, masterfader_value)
