view Syncopation models/basic_functions.py @ 22:2dbc09ca8013

Refactored KTH, not tested yet. Added conversion functions between note sequence and velocity sequence, and tostring functions. Renamed get_min_timeSpan into velocity_sequence_to_min_timetpan, so need to refactor that.
author Chunyang Song <csong@eecs.qmul.ac.uk>
date Thu, 09 Apr 2015 23:49:16 +0100
parents b6daddeefda9
children df1e7c378ee0
line wrap: on
line source
# This python file is a collection of basic functions that are used in the syncopation models. 

import math

# The concatenation function is used to concatenate two sequences.
def concatenate(seq1,seq2):
	return seq1+seq2

# The repetition function is to concatenate a sequence to itself for 'times' number of times.
def repeat(seq,times):
	new_seq = list(seq)
	if times >= 1:
		for i in range(times-1):
			new_seq = concatenate(new_seq,seq)
	else:
		#print 'Error: repetition times needs to be no less than 1.'
		new_seq = []
	return new_seq

# The subdivision function is to equally subdivide a sequence into 'divisor' number of segments.
def subdivide(seq,divisor):
	subSeq = []
	if len(seq) % divisor != 0:
		print 'Error: rhythmic sequence cannot be equally subdivided.'
	else:
		n = len(seq) / divisor
		start , end = 0, n
		for i in range(divisor):
			subSeq.append(seq[start : end])
			start = end
			end = end + n	
	return subSeq


# The ceiling function is to round each number inside a sequence up to its nearest integer.
def ceiling(seq):
	seq_ceil = []
	for s in seq:
		seq_ceil.append(int(math.ceil(s)))
	return seq_ceil

# The find_divisor function returns a list of all possible divisors for a length of sequence.
def find_divisor(number):
	divisors = [1]
	for i in range(2,number+1):
		if number%i ==0:
			divisors.append(i)
	return divisors

# The find_divisor function returns a list of all possible divisors for a length of sequence.
def find_prime_factors(number):
	primeFactors = find_divisor(number)
	
	# remove 1 because 1 is not prime number
	del primeFactors[0]

	# reversely traverse all the divisors list and once find a non-prime then delete
	for i in range(len(primeFactors)-1,0,-1):
	#	print primeFactors[i], is_prime(primeFactors[i])
		if not is_prime(primeFactors[i]):
			del primeFactors[i]

	return primeFactors

def is_prime(number):
	isPrime = True
	# 0 or 1 is not prime numbers
	if number < 2:
		isPrime = False
	# 2 is the only even prime number
	elif number == 2:
		pass
	# all the other even numbers are non-prime
	elif number % 2 == 0:
		isPrime = False
	else:
		for odd in range(3, int(math.sqrt(number) + 1), 2):
			if number % odd == 0:
				isPrime = False
	return isPrime

# convert a velocity sequence to its minimum time-span representation
def velocity_sequence_to_min_timetpan(velocitySequence):
	minTimeSpanVelocitySeq = [1]
	for divisors in find_divisor(len(velocitySequence)):
		segments = subdivide(velocitySequence,divisors)
		if len(segments)!=0:
			del minTimeSpanSequence[:]
			for s in segments:
				minTimeSpanVelocitySeq.append(s[0])
			if sum(minTimeSpanVelocitySeq) == sum(velocitySequence):
				break
	return minTimeSpanVelocitySeq

# convert a note sequence to its minimum time-span representation
def note_sequence_to_min_timespan(noteSequence, barTicks):
	barBinaryArray = [0]*barTicks
	for note in noteSequence:
		# mark note_on event (i.e. startTime) and note_off event (i.e. endTime = startTime + duration) as 1 in the barBinaryArray
		barBinaryArray[note[0]] = 1
		barBinaryArray[note[0]+note[1]] = 1

	# convert the barBinaryArray to its minimum time-span representation
	minBarBinaryArray = velocitySequence_to_min_timeSpan(barBinaryArray)
	delta_t = len(barBinaryArray)/len(minBarBinaryArray)

	# scale the startTime and duration of each note by delta_t
	for note in noteSequence:
		note[0] = note[0]/delta_t
		note[1] = note[1]/delta_t

	return noteSequence



# get_note_indices returns all the indices of all the notes in this velocity_sequence
def get_note_indices(velocitySequence):
	noteIndices = []

	for index in range(len(velocitySequence)):
		if velocitySequence[index] != 0:
			noteIndices.append(index)

	return noteIndices


# The get_H returns a sequence of metrical weight for a certain metrical level (horizontal),
# given the sequence of metrical weights in a hierarchy (vertical) and a sequence of subdivisions.
def get_H(weightSequence,subdivisionSequence, level):
	H = []
	#print len(weight_seq), len(subdivision_seq), level
	if (level <= len(subdivisionSequence)-1) and (level <= len(weightSequence)-1):
		if level == 0:
			H = repeat([weightSequence[0]],subdivisionSequence[0])
		else:
			H_pre = get_H(weightSequence,subdivisionSequence,level-1)
			for h in H_pre:
				H = concatenate(H, concatenate([h], repeat([weightSequence[level]],subdivisionSequence[level]-1)))
	else:
		print 'Error: a subdivision factor or metrical weight is not defined for the request metrical level.'
	return H


def calculate_bar_ticks(numerator, denominator, ticksPerQuarter):
	return (numerator * ticksPerQuarter *4) / denominator


def get_rhythm_category(velocitySequence, subdivisionSequence):
	'''
	The get_rhythm_category function is used to detect rhythm category: monorhythm or polyrhythm.
	For monorhythms, all prime factors of the length of minimum time-span representation of this sequence are
	elements of its subdivision_seq, otherwise it is polyrhythm; 
	e.g. prime_factors of polyrhythm 100100101010 in 4/4 is [2,3] but subdivision_seq = [1,2,2] for 4/4 
	'''
	rhythmCategory = 'mono'
	for f in find_prime_factors(len(get_min_timeSpan(velocitySequence))):
		if not (f in subdivisionSequence): 
			rhythmCategory = 'poly'
			break
	return rhythmCategory


def string_to_sequence(inputString):
	return map(int, inputString.split(','))

# # The get_subdivision_seq function returns the subdivision sequence of several common time-signatures defined by GTTM, 
# # or ask for the top three level of subdivision_seq manually set by the user.
# def get_subdivision_seq(timesig, L_max):
# 	subdivision_seq = []

# 	if timesig == '2/4' or timesig == '4/4':
# 		subdivision_seq = [1,2,2]
# 	elif timesig == '3/4' or timesig == '3/8':
# 		subdivision_seq = [1,3,2]
# 	elif timesig == '6/8':
# 		subdivision_seq = [1,2,3]
# 	elif timesig == '9/8':
# 		subdivision_seq = [1,3,3]
# 	elif timesig == '12/8':
# 		subdivision_seq = [1,4,3]
# 	elif timesig == '5/4' or timesig == '5/8':
# 		subdivision_seq = [1,5,2]
# 	elif timesig == '7/4' or timesig == '7/8':
# 		subdivision_seq = [1,7,2]
# 	elif timesig == '11/4' or timesig == '11/8':
# 		subdivision_seq = [1,11,2]
# 	else:
# 		print 'Time-signature',timesig,'is undefined. Please indicate subdivision sequence for this requested time-signature, e.g. [1,2,2] for 4/4 meter.'
# 		for i in range(3):
# 			s = int(input('Enter the subdivision factor at metrical level '+str(i)+':'))
# 			subdivision_seq.append(s)

# 	if L_max > 2:
# 		subdivision_seq = subdivision_seq + [2]*(L_max-2)
# 	else:
# 		subdivision_seq = subdivision_seq[0:L_max+1]
	
# 	return subdivision_seq


 # The split_by_bar function seperates the score representation of rhythm by bar lines, 
 # resulting in a list representingbar-by-bar rhythm sequence,
 # e.g. rhythm = ['|',[ts1,td1,v1], [ts2,td2,v2], '|',[ts3,td3,v3],'|'...]
 # rhythm_bybar = [ [ [ts1,td1,v1], [ts2,td2,v2] ], [ [ts3,td3,v3] ], [...]]
# def split_by_bar(rhythm):
# 	rhythm_bybar = []
# 	bar_index = []
# 	for index in range(len(rhythm)):
# 		if rhythm[index] == '|':

# 	return rhythm_bybar

# def yseq_to_vseq(yseq):
# 	vseq = []

# 	return vseq


# # testing
# print find_prime_factors(10)
# print find_prime_factors(2)
# print find_prime_factors(12)


# print is_prime(1)       # False
# print is_prime(2)       # True
# print is_prime(3)       # True
# print is_prime(29)      # True
# print is_prime(345)     # False
# print is_prime(999979)  # True
# print is_prime(999981)  # False