def train(self, mode_name, pitch_files, tonic_freqs, metric='pcd', save_dir=''):
		"""-------------------------------------------------------------------------
		For the mode trainings, the requirements are a set of recordings with 
		annotated tonics for each mode under consideration. This function only
		expects the recordings' pitch tracks and corresponding tonics as lists.
		The two lists should be indexed in parallel, so the tonic of ith pitch
		track in the pitch track list should be the ith element of tonic list.
		Once training is completed for a mode, the model wouldbe generated as a 
		PitchDistribution object and saved in a JSON file. For loading these objects
		and other relevant information about the data structure, see the
		PitchDistribution class.
		----------------------------------------------------------------------------
		mode_name     : Name of the mode to be trained. This is only used for naming
						the resultant JSON file, in the form "mode_name.json"
		pitch_files   : List of files with pitch tracks extracted from the recording
						(i.e. single-column files with frequencies)
		tonic_freqs   : List of annotated tonic frequencies of recordings
		metric        : Whether the model should be octave wrapped (Pitch Class
						Distribution: PCD) or not (Pitch Distribution: PD)
		save_dir      : Where to save the resultant JSON files.
		-------------------------------------------------------------------------"""

		# To generate the model pitch distribution of a mode, pitch track of each
		# recording is iteratively converted to cents, according to their respective
		# annotated tonics. Then, these are appended to mode_track and a very long
		# pitch track is generated, as if it is a single very long recording. The 
		# pitch distribution of this track is the mode's model distribution.

		# Normalize the pitch tracks of the mode wrt the tonic frequency and concatenate
		for pf, tonic in zip(pitch_files, tonic_freqs):
			pitch_track = np.loadtxt(pf)
			if pitch_track.ndim > 1:  # assume the first col is time, the second is pitch and the rest is labels etc
				pitch_track = pitch_track[:,1]

			if self.chunk_size == 0:  # use the complete pitch track
				mode_track = mF.hz_to_cent(pitch_track, ref_freq=tonic)
			else:  # slice and used the start of the pitch track
				time_track = np.arange(0, self.frame_rate * len(pitch_track), self.frame_rate)
				pitch_track, segs = mF.slice(time_track, pitch_track, mode_name, self.chunk_size)
				mode_track = mF.hz_to_cent(pitch_track[0], ref_freq=tonic)

		seglen = 'all' if self.chunk_size == 0 else (segs[0][1], segs[0][2])

		# generate the pitch distribution
		pitch_distrib = mF.generate_pd(mode_track, smooth_factor=self.smooth_factor,
		                               step_size=self.step_size, source=pitch_files, segment=seglen)
		if metric == 'pcd':  # convert to pitch class distribution, if specified
			pitch_distrib = mF.generate_pcd(pitch_distrib)

		# save the model to a file, if requested
		if save_dir:
			if not os.path.exists(save_dir):
				os.makedirs(save_dir)
			pitch_distrib.save(mode_name + '.json', save_dir=save_dir)

		return pitch_distrib
	def train(self, mode_name, pt_files, tonic_freqs, metric='pcd', save_dir=''):
		"""-------------------------------------------------------------------------
		For the mode trainings, the requirements are a set of recordings with 
		annotated tonics for each mode under consideration. This function only
		expects the recordings' pitch tracks and corresponding tonics as lists.
		The two lists should be indexed in parallel, so the tonic of ith pitch
		track in the pitch track list should be the ith element of tonic list.

		Each pitch track would be sliced into chunks of size chunk_size and their
		pitch distributions are generated. Then, each of such chunk distributions
		are appended to a list. This list represents the mode by sample points as
		much as the number of chunks. So, the result is a list of PitchDistribution
		objects, i.e. list of structured dictionaries and this is what is saved. 
		----------------------------------------------------------------------------
		mode_name     : Name of the mode to be trained. This is only used for naming
		                the resultant JSON file, in the form "mode_name.json"
		pt_files       : List of pitch tracks (i.e. 1-D list of frequencies)
		tonic_freqs : List of annotated tonics of recordings
		metric        : Whether the model should be octave wrapped (Pitch Class
			            Distribution: PCD) or not (Pitch Distribution: PD)
		save_dir      : Where to save the resultant JSON files.
		-------------------------------------------------------------------------"""
		save_name = mode_name + '.json'
		pitch_distrib_list = []

		# Each pitch track is iterated over and its pitch distribution is generated
		# individually, then appended to pitch_distrib_list. Notice that although we treat
		# each chunk individually, we use a single tonic annotation for each recording
		# so we assume that the tonic doesn't change throughout a recording.

		for pf, tonic in zip(pt_files, tonic_freqs):
			pitch_track = np.loadtxt(pf)
			if pitch_track.ndim > 1:  # assume the first col is time, the second is pitch and the rest is labels etc
				pitch_track = pitch_track[:,1]
			time_track = np.arange(0, (self.frame_rate*len(pitch_track)), self.frame_rate)

			# Current pitch track is sliced into chunks.
			if self.chunk_size == 0: # no slicing
				pts = [pitch_track]
				chunk_data = [pf + '_all']
			else:
				pts, chunk_data = mf.slice(time_track, pitch_track, pf, self.chunk_size,
				                           self.threshold, self.overlap)

			# Each chunk is converted to cents
			pts = [mf.hz_to_cent(k, ref_freq=tonic) for k in pts]

			# This is a wrapper function. It iteratively generates the distribution
			# for each chunk and return it as a list. After this point, we only
			# need to save it. God bless modular programming!
			temp_list = self.train_chunks(pts, chunk_data, tonic, metric)

			# The list is composed of lists of PitchDistributions. So,
			# each list in temp_list corresponds to a recording and each
			# PitchDistribution in that list belongs to a chunk. Since these
			# objects remember everything, we just flatten the list and make
			# life much easier. From now on, each chunk is treated as an individual
			# distribution, regardless of which recording it belongs to.
			for tmp in temp_list:
				pitch_distrib_list.append(tmp)

		# save the model to a file, if requested
		if save_dir:
			if not os.path.exists(save_dir):
				os.makedirs(save_dir)

			# Dump the list of dictionaries in a JSON file.
			dist_json = [{'bins':d.bins.tolist(), 'vals':d.vals.tolist(),
			              'kernel_width':d.kernel_width, 'source':d.source,
			              'ref_freq':d.ref_freq, 'segmentation':d.segmentation,
			              'overlap':d.overlap} for d in pitch_distrib_list]

			with open(os.path.join(save_dir, mode_name + '.json'), 'w') as f:
				json.dump(dist_json, f, indent=2)

		return pitch_distrib_list
	def estimate(self, pitch_file, mode_names=[], mode_name='', mode_dir='./', est_mode=True,
		         distance_method="euclidean", metric='pcd', tonic_freq=None,
		         k_param=1, equalSamplePerMode = False):
		"""-------------------------------------------------------------------------
		In the estimation phase, the input pitch track is sliced into chunk and each
		chunk is compared with each candidate mode's each sample model, i.e. with 
		the distributions of each training recording's each chunk. This function is
		a wrapper, that handles decision making portion and the overall flow of the
		estimation process. Internally, segment estimate is called for generation of
		distance matrices and detecting neighbor distributions.

		1) Joint Estimation: Neither the tonic nor the mode of the recording is known.
		Then, joint estimation estimates both of these parameters without any prior
		knowledge about the recording.
		To use this: est_mode and est_tonic flags should be True since both are to
		be estimated. In this case tonic_freq and mode_name parameters are not used,
		since these are used to pass the annotated data about the recording.

		2) Tonic Estimation: The mode of the recording is known and tonic is to be
		estimated. This is generally the most accurate estimation among the three.
		To use this: est_tonic should be True and est_mode should be False. In this
		case tonic_freq  and mode_names parameters are not used since tonic isn't
		known a priori and mode is known and hence there is no candidate mode.

		3) Mode Estimation: The tonic of the recording is known and mode is to be
		estimated.
		To use this: est_mode should be True and est_tonic should be False. In this
		case mode_name parameter isn't used since the mode annotation is not
		available. It can be ignored.
		----------------------------------------------------------------------------
		pitch_file      : File in which the pitch track of the input recording
						whose tonic and/or mode is to be estimated.
		mode_dir        : The directory where the mode models are stored. This is to
						load the annotated mode or the candidate mode.
		mode_names      : Names of the candidate modes. These are used when loading
						the mode models. If the mode isn't estimated, this parameter
						isn't used and can be ignored.
		mode_name       : Annotated mode of the recording. If it's not known and to be
						estimated, this parameter isn't used and can be ignored.
		est_tonic       : Whether tonic is to be estimated or not. If this flag is
						False, tonic_freq is treated as the annotated tonic.
		est_mode        : Whether mode is to be estimated or not. If this flag is
						False, mode_name is treated as the annotated mode.
		k_param         : The k parameter of K Nearest Neighbors. 
		distance_method : The choice of distance methods. See distance() in
						ModeFunctions for more information.
		metric          : Whether the model should be octave wrapped (Pitch Class
						Distribution: PCD) or not (Pitch Distribution: PD)
		tonic_freq        : Annotated tonic of the recording. If it's unknown, we use
						an arbitrary value, so this can be ignored.
		-------------------------------------------------------------------------"""
		# load pitch track
		pitch_track = np.loadtxt(pitch_file)

		# assume the first col is time, the second is pitch and the rest is labels etc.
		pitch_track = pitch_track[:,1] if pitch_track.ndim > 1 else pitch_track

		# Pitch track is sliced into chunks.
		time_track = np.arange(0, (self.frame_rate*len(pitch_track)), self.frame_rate)

		if self.chunk_size == 0: # no slicing
			pts = [pitch_track]
			chunk_data = ['input_all']
		else:
			pts, chunk_data = mf.slice(time_track, pitch_track, 'input', self.chunk_size,
				                 self.threshold, self.overlap)


		# Here's a neat trick. In order to return an estimation about the entire
		# recording based on our observations on individual chunks, we look at the
		# nearest neighbors of  union of all chunks. We are returning min_cnt
		# many number of closest neighbors from each chunk. To make sure that we
		# capture all of the nearest neighbors, we return a little more than
		# required and then treat the union of these nearest neighbors as if it's
		# the distance matrix of the entire recording.Then, we find the nearest
		# neighbors from the union of these from each chunk. This is quite an
		# overshoot, we only need min_cnt >= k_param. 

		### TODO: shrink this value as much as possible.
		min_cnt = len(pts) * k_param

		#Initializations
		tonic_list = 0
		mode_list = ''

		# parse tonic input
		if tonic_freq:  # tonic is already known;
			est_tonic = False
		else:
			est_tonic = True
			# take A4 as the dummy frequency value for cent conversion
			tonic_freq = 440

		if not (est_tonic or est_mode):
			print "Both tonic and mode are known!"
			return -1

		if(est_tonic and est_mode):
			neighbors = [ [mode_list, tonic_list] for i in range(len(chunk_data)) ]
		elif(est_tonic):
			neighbors = [ tonic_list for i in range(len(chunk_data)) ]
		elif(est_mode):
			neighbors = [ mode_list for i in range(len(chunk_data)) ]

		# chunk_estimate() generates the distributions of each chunk iteratively,
		# then compares it with all candidates and returns min_cnt closest neighbors
		# of each chunk to neighbors list.
		for p in range(len(pts)):
			neighbors[p] = self.chunk_estimate(pts[p], mode_names=mode_names,
			                                   mode_name=mode_name, mode_dir=mode_dir,
				                               est_tonic=est_tonic, est_mode=est_mode,
				                               distance_method=distance_method,
				                               metric=metric, ref_freq=tonic_freq,
				                               min_cnt=min_cnt,
				                               equalSamplePerMode = equalSamplePerMode)
		
		### TODO: Clean up the spaghetti decision making part. The procedures
		### are quite repetitive. Wrap them up with a separate function.

		# Temporary variables used during the desicion making part.
		candidate_distances, candidate_ests, candidate_sources, kn_distances, kn_ests, \
		kn_sources, idx_counts, elem_counts, res_distances, res_sources = ([] for i in range(10))

		# Joint estimation decision making. 
		if(est_mode and est_tonic):
			# Flattens the returned candidates and related data about them and
			# stores them into candidate_* variables. candidate_distances stores
			# the distance values, candidate_ests stores the mode/tonic pairs
			# candidate_sources stores the sources of the nearest neighbors.
			for i in xrange(len(pts)):
				for j in neighbors[i][1]:
					candidate_distances.append(j)
				for l in xrange(len(neighbors[i][0][1])):
					candidate_ests.append((neighbors[i][0][1][l], neighbors[i][0][0][l][0]))
					candidate_sources.append(neighbors[i][0][0][l][1])

			# Finds the nearest neighbors and fills all related data about
			# them to kn_* variables. Each of these variables have length k.
			# kn_distances stores the distance values, kn_ests stores
			# mode/tonic pairs, kn_sources store the name/id of the distribution
			# that gave rise to the corresponding distances.
			for k in xrange(k_param):
				idx = np.argmin(candidate_distances)
				kn_distances.append(candidate_distances[idx])
				kn_ests.append(candidate_ests[idx])
				kn_sources.append(candidate_sources[idx])
				candidate_distances[idx] = (np.amax(candidate_distances) + 1)
			
			# Counts the occurences of each candidate mode/tonic pair in
			# the K nearest neighbors. The result is our estimation. 
			for c in set(kn_ests):
				idx_counts.append(kn_ests.count(c))
				elem_counts.append(c)
			joint_estimation = elem_counts[np.argmax(idx_counts)]

			# We have concluded our estimation. Here, we retrieve the 
			# relevant data to this estimation; the sources and coresponding
			# distances.
			for m in xrange(len(kn_ests)):
				if (kn_ests[m] == joint_estimation):
					res_sources.append(kn_sources[m])
					res_distances.append(kn_distances[m])
			result = [joint_estimation, res_sources, res_distances]

		# Mode estimation decision making
		elif(est_mode):
			# Flattens the returned candidates and related data about them and
			# stores them into candidate_* variables. candidate_distances stores
			# the distance values, candidate_ests stores the candidate modes
			# candidate_sources stores the sources of the nearest neighbors.
			for i in xrange(len(pts)):
				for j in neighbors[i][1]:
					candidate_distances.append(j)
				for l in xrange(len(neighbors[i][0])):
					candidate_ests.append(neighbors[i][0][l][0])
					candidate_sources.append(neighbors[i][0][l][1])

			# Finds the nearest neighbors and fills all related data about
			# them to kn_* variables. Each of these variables have length k.
			# kn_distances stores the distance values, kn_ests stores
			# mode names, kn_sources store the name/id of the distributions
			# that gave rise to the corresponding distances.
			for k in xrange(k_param):
				idx = np.argmin(candidate_distances)
				kn_distances.append(candidate_distances[idx])
				kn_ests.append(candidate_ests[idx])
				kn_sources.append(candidate_sources[idx])
				candidate_distances[idx] = (np.amax(candidate_distances) + 1)

			# Counts the occurences of each candidate mode name in
			# the K nearest neighbors. The result is our estimation. 
			for c in set(kn_ests):
				idx_counts.append(kn_ests.count(c))
				elem_counts.append(c)
			mode_estimation = elem_counts[np.argmax(idx_counts)]

			# We have concluded our estimation. Here, we retrieve the 
			# relevant data to this estimation; the sources and coresponding
			# distances.
			for m in xrange(len(kn_ests)):
				if (kn_ests[m] == mode_estimation):
					res_sources.append(kn_sources[m])
					res_distances.append(kn_distances[m])
			result = [mode_estimation, res_sources, res_distances]


		# Tonic estimation decision making
		elif(est_tonic):
			# Flattens the returned candidates and related data about them and
			# stores them into candidate_* variables. candidate_distances stores
			# the distance values, candidate_ests stores the candidate peak 
			# frequencies, candidate_sources stores the sources of the nearest
			# neighbors.
			for i in xrange(len(pts)):
				for j in neighbors[i][1]:
					candidate_distances.append(j)
				for l in xrange(len(neighbors[i][0])):
					candidate_ests.append(neighbors[i][0][l][0])
					candidate_sources.append(neighbors[i][0][l][1])

			# Finds the nearest neighbors and fills all related data about
			# them to kn_* variables. Each of these variables have length k.
			# kn_distances stores the distance values, kn_ests stores
			# peak frequencies, kn_sources store the name/id of the
			# distributions that gave rise to the corresponding distances.
			for k in xrange(k_param):
				idx = np.argmin(candidate_distances)
				kn_distances.append(candidate_distances[idx])
				kn_ests.append(candidate_ests[idx])
				kn_sources.append(candidate_sources[idx])
				candidate_distances[idx] = (np.amax(candidate_distances) + 1)

			# Counts the occurences of each candidate tonic frequency in
			# the K nearest neighbors. The result is our estimation. 
			for c in set(kn_ests):
				idx_counts.append(kn_ests.count(c))
				elem_counts.append(c)
			tonic_estimation = elem_counts[np.argmax(idx_counts)]

			# We have concluded our estimation. Here, we retrieve the 
			# relevant data to this estimation; the sources and coresponding
			# distances.
			for m in xrange(len(kn_ests)):
				if (kn_ests[m] == tonic_estimation):
					res_sources.append(kn_sources[m])
					res_distances.append(kn_distances[m])
			result = [tonic_estimation, res_sources, res_distances]

		return result
	def estimate(self, pitch_file, mode_in='./', tonic_freq=None, rank=1,
	             distance_method="bhat", metric='pcd'):
		"""-------------------------------------------------------------------------
		This is the ultimate estimation function. There are three different types
		of estimations.

		1) Joint Estimation: Neither the tonic nor the mode of the recording is known.
		Then, joint estimation estimates both of these parameters without any prior
		knowledge about the recording.
		To use this: est_mode and est_tonic flags should be True since both are to
		be estimated. In this case tonic_freq and mode_name parameters are not used,
		since these are used to pass the annotated data about the recording.

		2) Tonic Estimation: The mode of the recording is known and tonic is to be
		estimated. This is generally the most accurate estimation among the three.
		To use this: est_tonic should be True and est_mode should be False. In this
		case tonic_freq  and mode_names parameters are not used since tonic isn't
		known a priori and mode is known and hence there is no candidate mode.

		3) Mode Estimation: The tonic of the recording is known and mode is to be
		estimated.
		To use this: est_mode should be True and est_tonic should be False. In this
		case mode_name parameter isn't used since the mode annotation is not
		available. It can be ignored.
		----------------------------------------------------------------------------
		pitch_file:     : File in which the pitch track of the input recording
						whose tonic and/or mode is to be estimated. 
		mode_in         : The mode input, If it is a filename or distribution object,
						the mode is treated as known and only tonic will be estimated.
						If a directory with the json files or dictionary of
						distributions (per mode) is given, the mode will be estimated.
						In case of directory, the modes will be taken as the json
						filenames.
		tonic_freq      : Annotated tonic of the recording. If it's unknown, we use
						an arbitrary value, so this can be ignored.
		rank            : The number of estimations expected from the system. If
						this is 1, estimation returns the most likely tonic, mode
						or tonic/mode pair. If it is n, it returns a sorted list
						of tuples of length n, each containing a tonic/mode pair. 
		distance_method : The choice of distance methods. See distance() in
						ModeFunctions for more information.
		metric          : Whether the model should be octave wrapped (Pitch Class
						Distribution: PCD) or not (Pitch Distribution: PD)
		-------------------------------------------------------------------------"""

		# load pitch track 
		pitch_track = np.loadtxt(pitch_file)

		# assume the first col is time, the second is pitch and the rest is labels etc.
		pitch_track = pitch_track[:,1] if pitch_track.ndim > 1 else pitch_track

		# parse mode input
		try:
			# list of json files per mode
			if all(os.path.isfile(m) for m in mode_in): 
				est_mode = True  # do mode estimation
				mode_names = [os.path.splitext(m)[0] for m in mode_in]
				models = [pD.load(m) for m in mode_in]
			elif os.path.isfile(mode_in): # json file
				est_mode = False # mode already known
				model = pD.load(mode_in)
		except TypeError:
			try:  # models
				if isinstance(mode_in, pD.PitchDistribution):
					# mode is loaded
					est_mode = False  # mode already known
					model = mode_in
				elif all(isinstance(m, pD.PitchDistribution) for m in mode_in.values()):
					# models of all modes are loaded
					est_mode = True  # do mode estimation
					mode_names = mode_in.keys()
					models = [mode_in[m] for m in mode_names]
			except:
				ValueError("Unknown mode input!")

		# parse tonic input
		if tonic_freq:  # tonic is already known;
			est_tonic = False
		else:
			est_tonic = True
			tonic_freq = 440  # take A4 as the dummy frequency value for cent conversion; it doesnt affect anything

		if not (est_tonic or est_mode):
			ValueError("Both tonic and mode are known!")

		# slice the pitch track if specified
		if self.chunk_size > 0:
			time_track = np.arange(0, self.frame_rate * len(pitch_track), self.frame_rate)
			pitch_track, segs = mF.slice(time_track, pitch_track, '', self.chunk_size)

		# normalize pitch track according to the given tonic frequency
		cent_track = mF.hz_to_cent(pitch_track, ref_freq=tonic_freq)

		# Pitch distribution of the input recording is generated
		distrib = mF.generate_pd(cent_track, ref_freq=tonic_freq, smooth_factor=self.smooth_factor,
		                         step_size=self.step_size)

		# convert to PCD, if specified
		distrib = mF.generate_pcd(distrib) if (metric == 'pcd') else distrib

		# Saved mode models are loaded and output variables are initiated
		tonic_ranked = [('', 0) for x in range(rank)]
		mode_ranked = [('', 0) for x in range(rank)]

		# Preliminary steps for tonic identification
		if est_tonic:
			if metric == 'pcd':
				# If there happens to be a peak at the last (and first due to the circular
				# nature of PCD) sample, it is considered as two peaks, one at the end and
				# one at the beginning. To prevent this, we find the global minima (as it
				# is easy to compute) of the distribution and make it the new reference
				# frequency, i.e. shift it to the beginning.
				shift_factor = distrib.vals.tolist().index(min(distrib.vals))
				distrib = distrib.shift(shift_factor)

				# update to the new reference frequency after shift
				tonic_freq = mF.cent_to_hz([distrib.bins[shift_factor]], ref_freq=tonic_freq)[0]

				# Find the peaks of the distribution. These are the tonic candidates.
				peak_idxs, peak_vals = distrib.detect_peaks()
			elif metric == 'pD':
				# Find the peaks of the distribution. These are the tonic candidates
				peak_idxs, peak_vals = distrib.detect_peaks()

				# The number of samples to be shifted is the list [peak indices - zero bin]
				# origin is the bin with value zero and the shifting is done w.r.t. it.
				origin = np.where(distrib.bins == 0)[0][0]
				shift_idxs = [(idx - origin) for idx in peak_idxs]

		# Joint Estimation
		if (est_tonic and est_mode):
			if (metric == 'pD'):
				# Since PD lengths aren't equal, we zero-pad the distributions for comparison
				# tonic_estimate() of ModeFunctions just does that. It can handle only
				# a single column, so the columns of the matrix are iteratively generated
				dist_mat = np.zeros((len(shift_idxs), len(models)))
				for m, model in enumerate(models):
					dist_mat[:, m] = mF.tonic_estimate(distrib, shift_idxs, model, distance_method=distance_method,
					                                   metric=metric, step_size=self.step_size)
			elif (metric == 'pcd'):
				# PCD doesn't require any preliminary steps. Generate the distance matrix.
				# The rows are tonic candidates and columns are mode candidates.
				dist_mat = mF.generate_distance_matrix(distrib, peak_idxs, models, method=distance_method)

			# Distance matrix is ready now. For each rank, (or each pair of
			# tonic-mode estimate pair) the loop is iterated. When the first
			# best estimate is found it's changed to the worst, so in the
			# next iteration, the estimate would be the second best and so on.
			for r in range(min(rank, len(peak_idxs))):
				# The minima of the distance matrix is found. This is when the
				# distribution is the most similar to a mode distribution, according
				# to the corresponding tonic estimate. The corresponding tonic
				# and mode pair is our current estimate.
				min_row = np.where((dist_mat == np.amin(dist_mat)))[0][0]
				min_col = np.where((dist_mat == np.amin(dist_mat)))[1][0]
				# Due to the precaution step of PCD, the reference frequency is
				# changed. That's why it's treated differently than PD. Here,
				# the cent value of the tonic estimate is converted back to Hz.
				if (metric == 'pcd'):
					tonic_ranked[r] = (mF.cent_to_hz([distrib.bins[peak_idxs[min_row]]],
					                                 tonic_freq)[0], dist_mat[min_row][min_col])
				elif (metric == 'pD'):
					tonic_ranked[r] = (mF.cent_to_hz([shift_idxs[min_row] * self.step_size],
					                                 tonic_freq)[0], dist_mat[min_row][min_col])
				# Current mode estimate is recorded.
				mode_ranked[r] = (mode_names[min_col], dist_mat[min_row][min_col])
				# The minimum value is replaced with a value larger than maximum,
				# so we won't return this estimate pair twice.
				dist_mat[min_row][min_col] = (np.amax(dist_mat) + 1)
			return mode_ranked, tonic_ranked

		# Tonic Estimation
		elif (est_tonic):
			# This part assigns the special case changes to standard variables,
			# so that we can treat PD and PCD in the same way, as much as
			# possible. 
			peak_idxs = shift_idxs if (metric == 'pD') else peak_idxs
			tonic_freq = tonic_freq if (metric == 'pcd') else tonic_freq

			# Distance vector is generated. In the mode_estimate() function
			# of ModeFunctions, PD and PCD are treated differently and it
			# handles the special cases such as zero-padding. The mode is
			# already known, so there is only one model to be compared. Each
			# entry corresponds to one tonic candidate.
			distance_vector = mF.tonic_estimate(distrib, peak_idxs, model, distance_method=distance_method,
			                                    metric=metric, step_size=self.step_size)

			# Distance vector is ready now. For each rank, the loop is iterated.
			# When the first best estimate is found it's changed to be the worst,
			# so in the next iteration, the estimate would be the second best
			# and so on
			for r in range(min(rank, len(peak_idxs))):
				# Minima is found, corresponding tonic candidate is our current
				# tonic estimate
				idx = np.argmin(distance_vector)
				# Due to the changed reference frequency in PCD's precaution step,
				# PCD and PD are treated differently here. 
				# TODO: review here, this might be tedious due to 257th line.
				if (metric == 'pcd'):
					tonic_ranked[r] = (mF.cent_to_hz([distrib.bins[peak_idxs[idx]]],
					                                 tonic_freq)[0], distance_vector[idx])
				elif (metric == 'pD'):
					tonic_ranked[r] = (mF.cent_to_hz([shift_idxs[idx] * self.step_size],
					                                 tonic_freq)[0], distance_vector[idx])
				# Current minima is replaced with a value larger than maxima,
				# so that we won't return the same estimate twice.
				distance_vector[idx] = (np.amax(distance_vector) + 1)
			return tonic_ranked

		# Mode Estimation
		elif (est_mode):
			# Distance vector is generated. Again, mode_estimate() of
			# ModeFunctions handles the different approach required for
			# PCD and PD. Since tonic is known, the distributions aren't
			# shifted and are only compared to candidate mode models.
			distance_vector = mF.mode_estimate(distrib, models, distance_method=distance_method, metric=metric,
			                                   step_size=self.step_size)

			# Distance vector is ready now. For each rank, the loop is iterated.
			# When the first best estimate is found it's changed to be the worst,
			# so in the next iteration, the estimate would be the second best
			# and so on
			for r in range(min(rank, len(mode_names))):
				# Minima is found, corresponding mode candidate is our current
				# mode estimate
				idx = np.argmin(distance_vector)
				mode_ranked[r] = (mode_names[idx], distance_vector[idx])
				# Current minima is replaced with a value larger than maxima,
				# so that we won't return the same estimate twice.
				distance_vector[idx] = (np.amax(distance_vector) + 1)
			return mode_ranked

		else:
			# Nothing is expected to be estimated.
			return 0