# Applies naive fourier transform using actual piano frequencies, using a generated sin-wave audio spectrum over a longer period of time to find the start- and endpoints of the generated keys.

import math
import random
import threading

PIANO_KEYS = [
    # key, frequency(Hz), Wavelength (cm)
    ['C#0/Db0', 17.32, 1991.47], 
    ['D0', 18.35, 1879.69], 
    ['D#0/Eb0', 19.45, 1774.2], 
    ['E0', 20.6, 1674.62], 
    ['F0', 21.83, 1580.63], 
    ['F#0/Gb0', 23.12, 1491.91], 
    ['G0', 24.5, 1408.18], 
    ['G#0/Ab0', 25.96, 1329.14], 
    ['A0', 27.5, 1254.55], 
    ['A#0/Bb0', 29.14, 1184.13],
    ['B0', 30.87, 1117.67], 
    ['C1', 32.7, 1054.94], 
    ['C#1/Db1', 34.65, 995.73], 
    ['D1', 36.71, 939.85], 
    ['D#1/Eb1', 38.89, 887.1], 
    ['E1', 41.2, 837.31], 
    ['F1', 43.65, 790.31], 
    ['F#1/Gb1', 46.25, 745.96], 
    ['G1', 49.0, 704.09], 
    ['G#1/Ab1', 51.91, 664.57], 
    ['A1', 55.0, 627.27], 
    ['A#1/Bb1', 58.27, 592.07], 
    ['B1', 61.74, 558.84], 
    ['C2', 65.41, 527.47], 
    ['C#2/Db2', 69.3, 497.87], 
    ['D2', 73.42, 469.92], 
    ['D#2/Eb2', 77.78, 443.55], 
    ['E2', 82.41, 418.65], 
    ['F2', 87.31, 395.16], 
    ['F#2/Gb2', 92.5, 372.98], 
    ['G2', 98.0, 352.04], 
    ['G#2/Ab2', 103.83, 332.29], 
    ['A2', 110.0, 313.64], 
    ['A#2/Bb2', 116.54, 296.03], 
    ['B2', 123.47, 279.42], 
    ['C3', 130.81, 263.74], 
    ['C#3/Db3', 138.59, 248.93], 
    ['D3', 146.83, 234.96], 
    ['D#3/Eb3', 155.56, 221.77], 
    ['E3', 164.81, 209.33], 
    ['F3', 174.61, 197.58], 
    ['F#3/Gb3', 185.0, 186.49],
    ['G3', 196.0, 176.02], 
    ['G#3/Ab3', 207.65, 166.14], 
    ['A3', 220.0, 156.82], 
    ['A#3/Bb3', 233.08, 148.02], 
    ['B3', 246.94, 139.71], 
    ['C4', 261.63, 131.87], 
    ['C#4/Db4', 277.18, 124.47], 
    ['D4', 293.66, 117.48], 
    ['D#4/Eb4', 311.13, 110.89], 
    ['E4', 329.63, 104.66], 
    ['F4', 349.23, 98.79], 
    ['F#4/Gb4', 369.99, 93.24], 
    ['G4', 392.0, 88.01], 
    ['G#4/Ab4', 415.3, 83.07], 
    ['A4', 440.0, 78.41], 
    ['A#4/Bb4', 466.16, 74.01], 
    ['B4', 493.88, 69.85], 
    ['C5', 523.25, 65.93], 
    ['C#5/Db5', 554.37, 62.23], 
    ['D5', 587.33, 58.74], 
    ['D#5/Eb5', 622.25, 55.44], 
    ['E5', 659.25, 52.33], 
    ['F5', 698.46, 49.39], 
    ['F#5/Gb5', 739.99, 46.62], 
    ['G5', 783.99, 44.01], 
    ['G#5/Ab5', 830.61, 41.54], 
    ['A5', 880.0, 39.2], 
    ['A#5/Bb5', 932.33, 37.0], 
    ['B5', 987.77, 34.93],
    ['C6', 1046.5, 32.97], 
    ['C#6/Db6', 1108.73, 31.12], 
    ['D6', 1174.66, 29.37], 
    ['D#6/Eb6', 1244.51, 27.72], 
    ['E6', 1318.51, 26.17], 
    ['F6', 1396.91, 24.7], 
    ['F#6/Gb6', 1479.98, 23.31], 
    ['G6', 1567.98, 22.0], 
    ['G#6/Ab6', 1661.22, 20.77], 
    ['A6', 1760.0, 19.6], 
    ['A#6/Bb6', 1864.66, 18.5], 
    ['B6', 1975.53, 17.46], 
    ['C7', 2093.0, 16.48], 
    ['C#7/Db7', 2217.46, 15.56], 
    ['D7', 2349.32, 14.69], 
    ['D#7/Eb7', 2489.02, 13.86], 
    ['E7', 2637.02, 13.08], 
    ['F7', 2793.83, 12.35], 
    ['F#7/Gb7', 2959.96, 11.66], 
    ['G7', 3135.96, 11.0], 
    ['G#7/Ab7', 3322.44, 10.38],
    ['A7', 3520.0, 9.8], 
    ['A#7/Bb7', 3729.31, 9.25],
    ['B7', 3951.07, 8.73], 
    ['C8', 4186.01, 8.24], 
    ['C#8/Db8', 4434.92, 7.78], 
    ['D8', 4698.63, 7.34], 
    ['D#8/Eb8', 4978.03, 6.93], 
    ['E8', 5274.04, 6.54], 
    ['F8', 5587.65, 6.17], 
    ['F#8/Gb8', 5919.91, 5.83], 
    ['G8', 6271.93, 5.5], 
    ['G#8/Ab8', 6644.88, 5.19], 
    ['A8', 7040.0, 4.9], 
    ['A#8/Bb8', 7458.62, 4.63], 
    ['B8', 7902.13, 4.37]
] 


sampling_rate = 1024
audio_duration = 16
key_strokes = 32
key_max_duration = (1, 8)
key_interval = 0.5
thread_count = 8
analysis_frame = 1 #in seconds.
analysis_stepsize = 0.01 #in seconds


should_print = True

def togglePrint(message):
    if should_print: 
        print(message)


def generateSpectrum():
    spectrum = [0.0] * (sampling_rate * audio_duration)

    # selects the to-be-pressed pressed keys.
    key_count = int(random.random() * key_strokes + 1)
    pressed_keys = [[] * 3] * key_count

    # calculates some constants for the audio interval
    v = audio_duration / key_interval
    u = key_max_duration[1] / key_interval
    pi2 = math.pi * 2

    togglePrint("Generated Keys")
    for i in range(0, key_count): 
        # selects the pressed key.
        k = int(random.random() * len(PIANO_KEYS))

        # calculates its start and end time.
        s = math.floor(random.random() * v) * key_interval
        d = max(key_max_duration[0], math.floor(random.random() * u) * key_interval)
        e = min(s + d, audio_duration)

        # adds the keys stroke.
        key = (PIANO_KEYS[k], s, e)
        pressed_keys[i] = key
        togglePrint(str(k) + " - " + str(PIANO_KEYS[k]) + " : " + str((s, e)))

        f = float(key[1]) / sampling_rate
        l = 0; 
        rs = math.floor(s * sampling_rate)
        re = math.floor(e * sampling_rate)

        for j in range(rs, re):
            m = math.sin(pi2 * l)
            l += f
            spectrum[j] += m
            
    return spectrum, pressed_keys


class AnalaysisThread (threading.Thread): 
    def __init__(self, ts, te, spectrum, frequency):
        threading.Thread.__init__(self)
        self.ts = ts
        self.te = te
        self.spectrum = spectrum
        self.frequency = frequency

    def run(self):
        # defining constants.
        neg2pi = -2.0 * math.pi
        sample_size = analysis_frame * sampling_rate

        # acquiring parameters
        spectrum = self.spectrum
        frequency = self.frequency
        ts = self.ts 
        te = self.te

        j = ts
        while j < te: 
            ds = math.floor(j * sampling_rate)
            de = min(len(spectrum), ds + math.floor(analysis_frame * sampling_rate))
            fi = math.floor((j / audio_duration) * (audio_duration / analysis_stepsize))

            for k in range(0, len(PIANO_KEYS)): 
                # selects  the current key, 
                # and calculates its function.
                key = PIANO_KEYS[k]
                f = float(key[1]) / sampling_rate

                # containers for complex number sums.
                r = 0
                i = 0

                # iterates through the spectrum.
                for t in range(ds, de): 
                    # Applies inner FFT formula.
                    g = spectrum[t]
                    e = neg2pi * t * f
                    r += g * math.cos(e)
                    i += g * math.sin(e)

                # calulates the mean square root
                m = math.sqrt(r * r + i * i) / sample_size
                frequency[fi][k] = m

            j += analysis_stepsize
        return frequency


def spectrumToKeys(spectrum): 
    interval = (audio_duration - analysis_stepsize) / thread_count
    frequencies = [[0.0] * len(PIANO_KEYS)] * math.floor(audio_duration / analysis_stepsize)

    ts = [0] * thread_count

    for i in range(0, thread_count): 
        a = AnalaysisThread(i * interval, (i + 1) * interval, spectrum, frequencies)
        ts[i] = a
        a.start()

    for t in ts: 
        t.join()

    return frequencies




def main(): 
    spectrum, keys = generateSpectrum()

    file1 = open("out.csv","a")
    file1.truncate(0)
    for i in spectrum: 
        file1.write(str(i) + ",\n")
    file1.close()

    key_strokes = spectrumToKeys(spectrum)
    
    file2 = open("out1.csv", "a")
    file2.truncate(0)
    for i in range(0, len(key_strokes)): 
        e = key_strokes[i]
        s = str(e)
        file2.write(s[1:len(s)-2] + "\n")

if __name__ == "__main__": 
    main()