# Path: apicultor/machine_learning/cache.py
# class memoize:       
#     """
#     The memoize class stores returned results into a cache so that those can be used later on if the same case happens
#     """
#     def __init__(self, func, size = 96):
#         """
#         memoize class init
#         :param func: the function that you're going to decorate
#         :param size: your maximum cache size (in MB)
#         """    
#         self.func = func
#         self.known_keys = [] #a list of keys to save numpy arrays
#         self.known_values = [] #a list of values to save numpy results
#         self.size = int(size * 1e+6 if size else None) #size in bytes
#         self.size_copy = int(np.copy(self.size))
# 
#     def __call__(self, *args, **kwargs):
#         key = sha512(bytes(str((args, kwargs, len(args[1]), len(args[2]))), 'utf-8')).hexdigest()
#         if (not key in self.known_keys): #when an ammount of arguments can't be identified from keys
#             value = self.func(*args, **kwargs) #compute function
#             self.known_keys.append(key) #add the key to your list of keys
#             self.known_values.append(value) #add the value to your list of values
#             if self.size:
#                 self.size -= value.__sizeof__() #the assigned space has decreased
#                 if (value.__sizeof__() > self.size): #free cache when size of values goes beyond the size limit
#                     del self.known_keys
#                     del self.known_values
#                     del self.size          
#                     self.known_keys = []
#                     self.known_values = []
#                     self.size = self.size_copy 
#                     self.known_keys.append(key)
#                     self.known_values.append(value)
#             return value
#         else: #if you've already computed everything
#             i = self.known_keys.index(key) #find your key and return your values
#             return self.known_values[i]
# 
# Path: apicultor/segmentation/DoSegmentation.py
# def do_segmentation(audio_input, audio_input_from_filename = True, audio_input_from_array = False, sec_len = 6, save_file = True):
# 
#     lenght = int(sec_len) * 10
# 
#     if audio_input_from_filename == True:                                           
#         #x = read(audio_input)[0]
#         x = MonoLoader(filename = audio_input)()
#         #print(audio_input)
#     if (audio_input_from_filename == False) and audio_input_from_array == True:                                           
#         x = audio_input
# 
#     retriever = MIR(x, 44100)
# 
#     frame_size = 4096
# 
#     hop_size = 1024
#  
#     segments = [len(frame) / 44100 for frame in retriever.FrameGenerator()]
# 
#     output = []
#     for segment in segments:                                           
#         sample = int(segment*44100) 
#         output.append(x[:sample*lenght]) #extend duration of segment
# 
#     output = choice(output)                                           
# 
#     if save_file == True:                                          
#         baseName = os.path.splitext(audio_input)[0].split('/')[-1]                                                                       
#         outputFilename = 'samples'+'/'+baseName+'_sample'+'.wav'                                                       
#         # write_file(outputFilename, 44100, output)
#         MonoWriter(filename = outputFilename)(output)
#         print(("File generated: %s"%outputFilename))
#     if save_file == False:
#         return output


from ..machine_learning.cache import memoize
from ..segmentation.DoSegmentation import do_segmentation
from random import choice, randint
from ..utils.algorithms import *
from librosa.effects import time_stretch
from librosa.util import axis_sort
import numpy as np
import sklearn
import librosa
import scipy
    hand_a = lambda: hand_move(audio, rev_audio, prop, slices = [prop, prop])
    hand_b = lambda: hand_move(audio, rev_audio, prop, slices = [prop[:12], prop[:12]])
    hand_c = lambda: hand_move(audio, rev_audio, prop, slices = [prop[1:], prop])
    hand_d = lambda: hand_move(audio, rev_audio, prop, slices = [prop[1:12], prop[:12]])
    hand_e = lambda: hand_move(audio, rev_audio, prop, slices = [prop, prop[:2]])
    hand_f = lambda: hand_move(audio, rev_audio, prop, slices = [prop[:12], prop[2:12]])
    hand_g = lambda: hand_move(audio, rev_audio, prop, slices = [prop[:2], prop[2:]])
    hand_h = lambda: hand_move(audio, rev_audio, prop, slices = [prop[3:12], prop[3:12]])
    movs = [hand_a, hand_b, hand_c, hand_d, hand_e, hand_f, hand_g, hand_h]
    return np.concatenate([choice(movs)()])

def scratch_music(audio, coordinate = False):
    """ 
    Enclosing function that performs DJ scratching and crossfading to a signal. First of all an arbitrary ammount of times to scratch the audio is chosen to segment the sound n times and also to arbitrarily decide the scratching technique 
    :param audio: the signal you want to scratch 
    :param coordinate: to set this to True, a decision variable must be given (a distance) to use a velocity given a learned distance    
    :returns:                                                                                                         
      - scratched recording
    """
    dur = int(len(audio)/44100)
    if not dur >= 8:
        try:                                                      
            iterations = choice(list(range(int(dur / 2)))) + 1
        except IndexError as e: #audio is too short to be scratched
            pass
        finally:
            return audio
    else:
        iterations = choice(list(range(int(dur / 8)))) + 1
    for i in range(iterations):

# Path: apicultor/machine_learning/cache.py
# class memoize:       
#     """
#     The memoize class stores returned results into a cache so that those can be used later on if the same case happens
#     """
#     def __init__(self, func, size = 96):
#         """
#         memoize class init
#         :param func: the function that you're going to decorate
#         :param size: your maximum cache size (in MB)
#         """    
#         self.func = func
#         self.known_keys = [] #a list of keys to save numpy arrays
#         self.known_values = [] #a list of values to save numpy results
#         self.size = int(size * 1e+6 if size else None) #size in bytes
#         self.size_copy = int(np.copy(self.size))
# 
#     def __call__(self, *args, **kwargs):
#         key = sha512(bytes(str((args, kwargs, len(args[1]), len(args[2]))), 'utf-8')).hexdigest()
#         if (not key in self.known_keys): #when an ammount of arguments can't be identified from keys
#             value = self.func(*args, **kwargs) #compute function
#             self.known_keys.append(key) #add the key to your list of keys
#             self.known_values.append(value) #add the value to your list of values
#             if self.size:
#                 self.size -= value.__sizeof__() #the assigned space has decreased
#                 if (value.__sizeof__() > self.size): #free cache when size of values goes beyond the size limit
#                     del self.known_keys
#                     del self.known_values
#                     del self.size          
#                     self.known_keys = []
#                     self.known_values = []
#                     self.size = self.size_copy 
#                     self.known_keys.append(key)
#                     self.known_values.append(value)
#             return value
#         else: #if you've already computed everything
#             i = self.known_keys.index(key) #find your key and return your values
#             return self.known_values[i]
# 
# Path: apicultor/segmentation/DoSegmentation.py
# def do_segmentation(audio_input, audio_input_from_filename = True, audio_input_from_array = False, sec_len = 6, save_file = True):
# 
#     lenght = int(sec_len) * 10
# 
#     if audio_input_from_filename == True:                                           
#         #x = read(audio_input)[0]
#         x = MonoLoader(filename = audio_input)()
#         #print(audio_input)
#     if (audio_input_from_filename == False) and audio_input_from_array == True:                                           
#         x = audio_input
# 
#     retriever = MIR(x, 44100)
# 
#     frame_size = 4096
# 
#     hop_size = 1024
#  
#     segments = [len(frame) / 44100 for frame in retriever.FrameGenerator()]
# 
#     output = []
#     for segment in segments:                                           
#         sample = int(segment*44100) 
#         output.append(x[:sample*lenght]) #extend duration of segment
# 
#     output = choice(output)                                           
# 
#     if save_file == True:                                          
#         baseName = os.path.splitext(audio_input)[0].split('/')[-1]                                                                       
#         outputFilename = 'samples'+'/'+baseName+'_sample'+'.wav'                                                       
#         # write_file(outputFilename, 44100, output)
#         MonoWriter(filename = outputFilename)(output)
#         print(("File generated: %s"%outputFilename))
#     if save_file == False:
#         return output


# Path: apicultor/utils/dj.py
from ..machine_learning.cache import memoize
from ..segmentation.DoSegmentation import do_segmentation
from random import choice, randint
from ..utils.algorithms import *
from librosa.effects import time_stretch
from librosa.util import axis_sort
import numpy as np
import sklearn
import librosa
import scipy

    hand_a = lambda: hand_move(audio, rev_audio, prop, slices = [prop, prop])
    hand_b = lambda: hand_move(audio, rev_audio, prop, slices = [prop[:12], prop[:12]])
    hand_c = lambda: hand_move(audio, rev_audio, prop, slices = [prop[1:], prop])
    hand_d = lambda: hand_move(audio, rev_audio, prop, slices = [prop[1:12], prop[:12]])
    hand_e = lambda: hand_move(audio, rev_audio, prop, slices = [prop, prop[:2]])
    hand_f = lambda: hand_move(audio, rev_audio, prop, slices = [prop[:12], prop[2:12]])
    hand_g = lambda: hand_move(audio, rev_audio, prop, slices = [prop[:2], prop[2:]])
    hand_h = lambda: hand_move(audio, rev_audio, prop, slices = [prop[3:12], prop[3:12]])
    movs = [hand_a, hand_b, hand_c, hand_d, hand_e, hand_f, hand_g, hand_h]
    return np.concatenate([choice(movs)()])

def scratch_music(audio, coordinate = False):
    """ 
    Enclosing function that performs DJ scratching and crossfading to a signal. First of all an arbitrary ammount of times to scratch the audio is chosen to segment the sound n times and also to arbitrarily decide the scratching technique 
    :param audio: the signal you want to scratch 
    :param coordinate: to set this to True, a decision variable must be given (a distance) to use a velocity given a learned distance    
    :returns:                                                                                                         
      - scratched recording
    """
    dur = int(len(audio)/44100)
    if not dur >= 8:
        try:                                                      
            iterations = choice(list(range(int(dur / 2)))) + 1
        except IndexError as e: #audio is too short to be scratched
            pass
        finally:
            return audio
    else:
        iterations = choice(list(range(int(dur / 8)))) + 1
    for i in range(iterations):

        sound = do_segmentation(audio_input = np.float32(audio), audio_input_from_filename = False, audio_input_from_array = True, sec_len = choice(list(range(2,6))), save_file = False) 