[docs]classIndex:""" A class that performs various analysis on a list of values. Args: values (list): A list of numerical values. weights (list, optional): A list of weights corresponding to the values. Defaults to None. Attributes: values (list): A list of numerical values. weights (list): A list of weights corresponding to the values. """def__init__(self,values,weights=None):ifweightsisNone:self.weights=[1]*len(values)else:self.weights=weights# Pair values and weights to filter out None values jointlycleaned_data=[(v,w)forv,winzip(values,self.weights)ifvisnotNone]self.values,self.weights=zip(*cleaned_data)ifcleaned_dataelse([],[])# Initialize positions assuming sequential valuesself.positions=list(range(len(self.values)))
[docs]defgini(self):""" Calculates the Gini index of the values. Returns: float: The Gini index. """ifnotself.valuesorlen(self.values)<=1:returnNonevalues=[v*wforv,winzip(self.values,self.weights)]sorted_values=sorted(values)n=len(sorted_values)cumulative_sum=sum((i+1)*valfori,valinenumerate(sorted_values))total_sum=sum(sorted_values)gini=(2*cumulative_sum)/(n*total_sum)-(n+1)/nreturngini
[docs]defbalance(self):""" Calculates the balance index of the values. Returns: float: The balance index. """ifnotself.valuesorlen(self.values)<=1:returnNoneweighted_positions=sum(pos*weightforpos,weightinzip(self.positions,self.weights))total_weight=sum(self.weights)center_of_mass=weighted_positions/total_weighttotal_length_of_cycle=max(self.positions)+(self.weights[self.positions.index(max(self.positions))])ideal_center=total_length_of_cycle/2balance_index=abs(center_of_mass-ideal_center)/ideal_centerreturnbalance_index
[docs]defautocorrelation(self):""" Calculates the autocorrelation of the values. Returns: list: The autocorrelation values. """n=len(self.values)result=correlate(self.values,self.values,mode='full',method='fft')result=result[n-1:]/np.array([n-abs(i)foriinrange(n)])returnresult.tolist()
[docs]defmotif(self):""" Calculates the motif score of the values. Returns: float: The motif score. """ifnotself.values:# Check if the list is emptyreturn0# Or return another appropriate default valueautocorr=self.autocorrelation()peaks,_=find_peaks(autocorr)iflen(peaks)>1:motif_lengths=np.diff(peaks)# Distances between peaks as potential motif lengthsmotif_length=np.median(motif_lengths)# Use the median as a common motif lengthelse:return0# Return zero score if no motif length is identifiedmotif_length=int(motif_length)motif_counts={}foriinrange(len(self.values)-motif_length+1):motif=tuple(self.values[i:i+motif_length])motif_counts[motif]=motif_counts.get(motif,0)+1# Score based on the frequency of repeated motifsmotif_score=sum(countforcountinmotif_counts.values()ifcount>1)/len(self.values)returnmotif_score
[docs]defdissonance(self,scale):""" Calculates the dissonance of the values with respect to a scale. Args: scale (list): A list of values representing a musical scale. Returns: float: The dissonance. """ifnotself.values:# Check if the list is emptyreturn0# Or return another appropriate default valuen=0forvinself.values:ifvnotinscale:n+=1returnn/len(self.values)
[docs]defrhythmic(self,measure_length):""" Calculates the rhythmic score of the values. Args: measure_length (float): The length of a measure. Returns: float: The rhythmic score. """scores=[]current_measure_duration=0.0fordurationinself.values:current_measure_duration+=durationifcurrent_measure_duration>measure_length:# Here we consider overflow as a negative, so we reset for the next measurescores.append(0)# Score of 0 for overflowed measure, adjust based on your scoring preferencecurrent_measure_duration=duration# Start counting the new measureelifcurrent_measure_duration==measure_length:scores.append(1)# Perfect fit for this measurecurrent_measure_duration=0.0# Reset for the next measure# Handle last measure if it doesn't reach full measure_length but no more durations are availableif0<current_measure_duration<=measure_length:# The closer the last measure's total duration is to the measure_length, the betterscores.append(current_measure_duration/measure_length)# Return the average score if there are scores, else return 0 (indicating no fit)returnnp.mean(scores)ifscoreselse0
[docs]deffibonacci_index(self):""" Calculates a Fibonacci index to evaluate how closely the sequence matches a Fibonacci sequence. Returns: float: The Fibonacci index, lower values indicate closer match to Fibonacci sequence. """iflen(self.values)<3:returnfloat('inf')# Not enough data to compute Fibonacci likeness# Calculate ratios of consecutive numbersratios=[self.values[i]/self.values[i-1]foriinrange(1,len(self.values))ifself.values[i-1]!=0]# Calculate how these ratios deviate from the golden ratiogolden_ratio=(1+np.sqrt(5))/2deviations=[abs(ratio-golden_ratio)forratioinratios]# Calculate an index as the average of these deviationsfibonacci_index=sum(deviations)/len(deviations)returnfibonacci_index