1 /* FormantPath.cpp
2  *
3  * Copyright (C) 2020-2021 David Weenink
4  *
5  * This code is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or (at
8  * your option) any later version.
9  *
10  * This code is distributed in the hope that it will be useful, but
11  * WITHOUT ANY WARRANTY; without d the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this work. If not, see <http://www.gnu.org/licenses/>.
17  */
18 
19 #include "FormantPath.h"
20 #include "FormantPath_to_IntervalTier.h"
21 #include "FormantModeler.h"
22 #include "Formant_extensions.h"
23 #include "Graphics_extensions.h"
24 #include "LPC_and_Formant.h"
25 #include "Matrix.h"
26 #include "Sound_to_Formant.h"
27 #include "Sound_and_LPC.h"
28 #include "Sound.h"
29 #include "Sound_and_LPC_robust.h"
30 
31 #include "oo_DESTROY.h"
32 #include "FormantPath_def.h"
33 #include "oo_COPY.h"
34 #include "FormantPath_def.h"
35 #include "oo_EQUAL.h"
36 #include "FormantPath_def.h"
37 #include "oo_CAN_WRITE_AS_ENCODING.h"
38 #include "FormantPath_def.h"
39 #include "oo_WRITE_TEXT.h"
40 #include "FormantPath_def.h"
41 #include "oo_WRITE_BINARY.h"
42 #include "FormantPath_def.h"
43 #include "oo_READ_TEXT.h"
44 #include "FormantPath_def.h"
45 #include "oo_READ_BINARY.h"
46 #include "FormantPath_def.h"
47 #include "oo_DESCRIPTION.h"
48 #include "FormantPath_def.h"
49 
v_info()50 void structFormantPath :: v_info () {
51 	structDaata :: v_info ();
52 	MelderInfo_writeLine (U"Number of Formant objects: ", formants . size);
53 	for (integer ic = 1; ic <= ceilings.size; ic ++)
54 		MelderInfo_writeLine (U"Ceiling ", ic, U": ", ceilings [ic], U" Hz");
55 }
56 
v_getValueAtSample(integer iframe,integer which,int units)57 double structFormantPath :: v_getValueAtSample (integer iframe, integer which, int units) {
58 	const Formant formant = reinterpret_cast<Formant> (our formants.at [our path [iframe]]);
59 	return formant -> v_getValueAtSample (iframe, which, units);
60 }
61 
v_getUnitText(integer,int,uint32)62 conststring32 structFormantPath :: v_getUnitText (integer /*level*/, int /*unit*/, uint32 /*flags*/) {
63 	return U"Frequency (Hz)";
64 
65 };
66 
67 Thing_implement (FormantPath, Sampled, 0);
68 
FormantPath_getGridDimensions(FormantPath me,integer * out_nrow,integer * out_ncol)69 void FormantPath_getGridDimensions (FormantPath me, integer *out_nrow, integer *out_ncol) {
70 	integer ncol = 1;
71 	integer nrow = my ceilings.size;
72 	if (nrow > 3) {
73 		nrow = 1 + Melder_ifloor (sqrt (nrow - 0.5));
74 		ncol = 1 + Melder_ifloor ((my ceilings.size - 1.0) / nrow);
75 	}
76 	if (out_nrow)
77 		*out_nrow = nrow;
78 	if (out_ncol)
79 		*out_ncol = ncol;
80 }
81 
FormantPath_create(double xmin,double xmax,integer nx,double dx,double x1,integer numberOfCeilings)82 autoFormantPath FormantPath_create (double xmin, double xmax, integer nx, double dx, double x1, integer numberOfCeilings) {
83 	autoFormantPath me = Thing_new (FormantPath);
84 	Sampled_init (me.get (), xmin, xmax, nx, dx, x1);
85 	my ceilings = zero_VEC (numberOfCeilings);
86 	my path = zero_INTVEC (nx);
87 	return me;
88 }
89 
FormantPath_pathFinder(FormantPath me,double qWeight,double frequencyChangeWeight,double stressWeight,double ceilingChangeWeight,double intensityModulationStepSize,double windowLength,constINTVEC const & parameters,double powerf)90 void FormantPath_pathFinder (FormantPath me, double qWeight, double frequencyChangeWeight, double stressWeight, double ceilingChangeWeight,
91 	double intensityModulationStepSize, double windowLength, constINTVEC const& parameters, double powerf)
92 {
93 	autoINTVEC path = FormantPath_getOptimumPath (me, qWeight, frequencyChangeWeight, stressWeight, ceilingChangeWeight, intensityModulationStepSize, windowLength, parameters, powerf, nullptr);
94 	my path = path.move();
95 }
96 
FormantPath_getOptimumPath(FormantPath me,double qWeight,double frequencyChangeWeight,double stressWeight,double ceilingChangeWeight,double intensityModulationStepSize,double windowLength,constINTVEC const & parameters,double powerf,autoMatrix * out_delta)97 autoINTVEC FormantPath_getOptimumPath (FormantPath me, double qWeight, double frequencyChangeWeight, double stressWeight, double ceilingChangeWeight, double intensityModulationStepSize, double windowLength, constINTVEC const& parameters, double powerf, autoMatrix *out_delta) {
98 	constexpr double qCutoff = 20.0;
99 	constexpr double stressCutoff = 100.0;
100 	try {
101 		integer transtionCostType = 1;
102 		double transitionCostCuttoff = 100.0;
103 		if (Melder_debug == -3) {
104 			transtionCostType = 2;
105 			transitionCostCuttoff = 0.3;
106 		}
107 		autoMatrix stresses, qsums;
108 		MelderExtremaWithInit intensities;
109 		const integer midformant = (my formants.size + 1) / 2;
110 		if (intensityModulationStepSize > 0.0) {
111 			for (integer iframe = 1; iframe <= my nx; iframe ++) {
112 				const Formant_Frame frame = & my formants.at [midformant] -> frames [iframe];
113 				intensities.update (frame -> intensity);
114 			}
115 		}
116 		const bool hasIntensityDifference = ( intensities.max - intensities.min > 0.0 );
117 		const double dbMid = 0.5 * 10.0 * log10 (intensities.max * intensities.min);
118 		const integer maxnFormants = my formants.at [1] -> maxnFormants;
119 		const integer numberOfTracks = std::min (maxnFormants, parameters.size);
120 		if (qWeight > 0.0)
121 			qsums = FormantPath_to_Matrix_qSums (me, numberOfTracks);
122 		if (stressWeight > 0.0)
123 			stresses = FormantPath_to_Matrix_stress (me, windowLength, parameters, powerf);
124 
125 		autoINTMAT psi = zero_INTMAT (my formants.size, my nx);
126 		autoMatrix thee = Matrix_create (my xmin, my xmax, my nx, my dx, my x1, 0.5, my formants.size + 0.5, my formants.size, 1.0, 1.0);
127 		/*
128 			delta [i] [j] = minimum cost to reach state i at time j
129 		*/
130 		MAT delta (& thy z [1] [1], thy ny, thy nx);
131 		autoINTVEC path = zero_INTVEC (my nx);
132 		autoVEC intensity = raw_VEC (my nx);
133 		/*
134 			We have a trellis of size S x T, where S is the number of states, i.e. the number of formant objects,
135 			and T the number of frames (S= formants.size and T=nx).
136 			Evaluate the static costs for state s [i] at times t=1..T
137 			There are two components at each t:
138 				1. (+) the local stress at s [i] [t], which is evaluated from an interval around time t
139 				2. (-) the local qsums at s [i] [t], evaluated from the frequencies/bandwidths in s [i] [t]
140 			The sum of these components might be modulated by the local intensity
141 		*/
142 		for (integer itime = 1; itime <= my nx; itime ++) {
143 			for (integer iformant = 1; iformant <= my formants.size; iformant ++) {
144 				const Formant_Frame frame = & my formants.at [iformant] -> frames [itime];
145 				double wIntensity = 1.0, costs = 0.0;
146 				if (hasIntensityDifference && intensityModulationStepSize > 0.0) {
147 					if (frame -> intensity > 0.0) {
148 						const double dbi = 10.0 * log10 (frame -> intensity / 2e-5);
149 						wIntensity = NUMsigmoid ((dbi - dbMid) / intensityModulationStepSize);
150 					} else
151 						wIntensity = 0.0;
152 				}
153 				if (stressWeight > 0.0 && isdefined (stresses -> z [iformant] [itime]))
154 					costs += stressWeight * std::min (stresses -> z [iformant] [itime] / stressCutoff, 1.0);
155 				if (qWeight > 0.0)
156 					costs -= qWeight * std::min (qsums -> z [iformant] [itime] / qCutoff, 1.0);
157 				delta [iformant] [itime] += wIntensity * costs;
158 			}
159 		}
160 		/*
161 			Evaluate the costs delta [j] [t] going from s [i] [t-1] to s [j] [t]
162 			delta [j] [t] = min_over_i { delta [i] [t-1] + transition_costs (s [i] [t-1], s [j] [t] },
163 			where the transition_costs consists of two parts:
164 				3. (+) costs involving the sum of the distances between the formant frequencies in state s [i] [t-1] and s [j] [t]
165 				4. (+) costs involving the difference in ceiling [i] [t-1] and ceiling [j] [t]
166 				       (or should we measure the costs w.r.t the middle frequency?)
167 		*/
168 		const double ceilingsRange = my ceilings [my formants.size] - my ceilings [1];
169 		for (integer itime = 2; itime <= my nx; itime ++) {
170 			for (integer iformant = 1; iformant <= my formants.size; iformant++) {
171 				const Formant_Frame ffi = & my formants.at [iformant] -> frames [itime];
172 				const integer numberOfTracks_i = std::min (numberOfTracks, ffi -> numberOfFormants);
173 				double deltamin = 1e100;
174 				integer minPos = 0;
175 				for (integer jformant = 1; jformant <= my formants.size; jformant++) {
176 					const Formant_Frame ffj = & my formants.at [jformant] -> frames [itime - 1];
177 					const integer ntracks = std::min (ffj -> numberOfFormants, numberOfTracks_i);
178 					double transitionCosts = delta [jformant] [itime - 1];
179 					if (frequencyChangeWeight > 0.0) {
180 						double fcost = 0.0;
181 						for (integer itrack = 1; itrack <= ntracks; itrack ++) {
182 							const double fi = ffi -> formant [itrack]. frequency, fj = ffj -> formant [itrack]. frequency;
183 							if (transtionCostType == 1) {
184 								const double dif = fabs (fi  - fj);
185 								const double sum = fi  + fj;
186 								const double bw = sqrt (ffi -> formant [itrack]. bandwidth * ffj -> formant [itrack]. bandwidth);
187 								fcost += bw * dif / sum;
188 							} else
189 								fcost += fabs (NUMlog2 (fi / fj));
190 						}
191 						fcost /= ntracks;
192 						transitionCosts += frequencyChangeWeight * std::min (fcost / transitionCostCuttoff, 1.0);
193 					}
194 					if (ceilingChangeWeight > 0.0) {
195 						const double ceilingChangeCosts = fabs (my ceilings [iformant] - my ceilings [jformant]) / ceilingsRange;
196 						transitionCosts += ceilingChangeWeight * ceilingChangeCosts;
197 					}
198 					if (transitionCosts < deltamin) {
199 						deltamin = transitionCosts;
200 						minPos = jformant;
201 					}
202 				}
203 				if (stressWeight > 0.0 && isdefined (stresses -> z [iformant] [itime]))
204 					deltamin += stressWeight * std::min (stresses -> z [iformant] [itime] / stressCutoff, 1.0);
205 				if (qWeight > 0.0)
206 					deltamin -= qWeight * std::min (qsums -> z [iformant] [itime] / qCutoff, 1.0);
207 				delta [iformant] [itime] += deltamin;
208 				psi [iformant] [itime] = minPos;
209 			}
210 		}
211 		path [my nx] = NUMmaxPos (delta.column (my nx));
212 		/*
213 			Backtrack
214 		*/
215 		for (integer itime = my nx; itime > 1; itime --) {
216 			path [itime - 1] = psi [path [itime]] [itime];
217 		}
218 		if (out_delta)
219 			*out_delta = thee.move();
220 		return path;
221 	} catch (MelderError) {
222 		Melder_throw (me, U": cannot find path.");
223 	}
224 }
225 
FormantPath_extractFormant(FormantPath me)226 autoFormant FormantPath_extractFormant (FormantPath me) {
227 	Formant formant = my formants. at [1];
228 	autoFormant thee = Formant_create (my xmin, my xmax, my nx, my dx, my x1, formant -> maxnFormants);
229 	for (integer iframe = 1; iframe <= my path.size; iframe ++) {
230 		Formant source = reinterpret_cast <Formant> (my formants. at [my path [iframe]]);
231 		Formant_Frame targetFrame = & thy frames [iframe];
232 		Formant_Frame sourceFrame = & source -> frames [iframe];
233 		sourceFrame -> copy (targetFrame);
234 	}
235 	return thee;
236 }
237 
Sound_to_FormantPath_any(Sound me,kLPC_Analysis lpcType,double timeStep,double maximumNumberOfFormants,double middleCeiling,double analysisWidth,double preemphasisFrequency,double ceilingStepSize,integer numberOfStepsToACeiling,double marple_tol1,double marple_tol2,double huber_numberOfStdDev,double huber_tol,integer huber_maximumNumberOfIterations,autoSound * out_sourcesMultiChannel)238 autoFormantPath Sound_to_FormantPath_any (Sound me, kLPC_Analysis lpcType, double timeStep, double maximumNumberOfFormants,
239 	double middleCeiling, double analysisWidth, double preemphasisFrequency, double ceilingStepSize,
240 	integer numberOfStepsToACeiling, double marple_tol1, double marple_tol2, double huber_numberOfStdDev, double huber_tol,
241 	integer huber_maximumNumberOfIterations, autoSound *out_sourcesMultiChannel)
242 {
243 	try {
244 		Melder_require (timeStep > 0.0,
245 			U"The timeStep needs to greater than zero seconds.");
246 		Melder_require (ceilingStepSize > 0.0,
247 			U"The ceiling step size should larger than 0.0.");
248 		const double nyquistFrequency = 0.5 / my dx;
249 		const integer numberOfCeilings = 2 * numberOfStepsToACeiling + 1;
250 		const double maximumCeiling = middleCeiling *  exp (ceilingStepSize * numberOfStepsToACeiling);
251 		Melder_require (maximumCeiling <= nyquistFrequency,
252 			U"The maximum ceiling should be smaller than ", nyquistFrequency, U" Hz. "
253 			"Decrease the 'ceiling step size' or the 'number of steps' or both.");
254 		volatile double windowDuration = 2.0 * analysisWidth;
255 		if (windowDuration > my dx * my nx)
256 			windowDuration = my dx * my nx;
257 		/*
258 			Get the data for the LPC from the resampled sound with 'middleCeiling' as maximum frequency
259 			to make the sampling exactly equal as if performed with a standard LPC analysis.
260 		*/
261 		integer numberOfFrames;
262 		double t1;
263 		autoSound midCeiling = Sound_resample (me, 2.0 * middleCeiling, 50);
264 		Sampled_shortTermAnalysis (midCeiling.get(), windowDuration, timeStep, & numberOfFrames, & t1); // Gaussian window
265 		const integer predictionOrder = Melder_iround (2.0 * maximumNumberOfFormants);
266 		autoFormantPath thee = FormantPath_create (my xmin, my xmax, numberOfFrames, timeStep, t1, numberOfCeilings);
267 		autoSound multiChannelSound;
268 		if (out_sourcesMultiChannel)
269 			multiChannelSound = Sound_create (numberOfCeilings, midCeiling -> xmin, midCeiling -> xmax, midCeiling -> nx, midCeiling -> dx, midCeiling -> x1);
270 		const double formantSafetyMargin = 50.0;
271 		thy ceilings [numberOfStepsToACeiling + 1] = middleCeiling;
272 		for (integer ic  = 1; ic <= numberOfCeilings; ic ++) {
273 			autoFormant formant;
274 			if (ic <= numberOfStepsToACeiling)
275 				thy ceilings [ic] = middleCeiling * exp (-ceilingStepSize * (numberOfStepsToACeiling - ic + 1));
276 			else if (ic > numberOfStepsToACeiling + 1)
277 				thy ceilings [ic] = middleCeiling * exp ( ceilingStepSize * (ic - numberOfStepsToACeiling - 1));
278 			autoSound resampled;
279 			if (ic != numberOfStepsToACeiling + 1)
280 				resampled = Sound_resample (me, 2.0 * thy ceilings [ic], 50);
281 			else
282 				resampled = midCeiling.move();
283 			autoLPC lpc = LPC_create (my xmin, my xmax, numberOfFrames, timeStep, t1, predictionOrder, resampled -> dx);
284 			if (lpcType != kLPC_Analysis::ROBUST) {
285 				Sound_into_LPC (resampled.get(), lpc.get(), analysisWidth, preemphasisFrequency, lpcType, marple_tol1, marple_tol2);
286 			} else {
287 				Sound_into_LPC (resampled.get(), lpc.get(), analysisWidth, preemphasisFrequency, kLPC_Analysis::AUTOCORRELATION, marple_tol1, marple_tol2);
288 				lpc = LPC_Sound_to_LPC_robust (lpc.get(), resampled.get(), analysisWidth, preemphasisFrequency, huber_numberOfStdDev, huber_maximumNumberOfIterations, huber_tol, true);
289 			}
290 			formant = LPC_to_Formant (lpc.get(), formantSafetyMargin);
291 			thy formants . addItem_move (formant.move());
292 			if (out_sourcesMultiChannel) {
293 				autoSound source = LPC_Sound_filterInverse (lpc.get(), resampled.get ());
294 				autoSound source_resampled = Sound_resample (source.get(), 2.0 * middleCeiling, 50);
295 				const integer numberOfSamples = std::min (midCeiling -> nx, source_resampled -> nx);
296 				multiChannelSound -> z.row (ic).part (1, numberOfSamples)  <<=  source_resampled -> z.row (1).part (1, numberOfSamples);
297 			}
298 		}
299 		/*
300 			Maintain invariants
301 		*/
302 		Melder_assert (thy formants.size == numberOfCeilings);
303 		thy path = raw_INTVEC (thy nx);
304 		for (integer i = 1; i <= thy path.size; i++)
305 			thy path [i] = numberOfStepsToACeiling + 1;
306 		if (out_sourcesMultiChannel)
307 			*out_sourcesMultiChannel = multiChannelSound.move();
308 		return thee;
309 	} catch (MelderError) {
310 		Melder_throw (me, U": FormantPath not created.");
311 	}
312 }
313 
FormantPath_to_Matrix_qSums(FormantPath me,integer numberOfTracks)314 autoMatrix FormantPath_to_Matrix_qSums (FormantPath me, integer numberOfTracks) {
315 	try {
316 		autoMatrix thee = Matrix_create (my xmin, my xmax, my nx, my dx, my x1, 0.5, my formants.size + 0.5, my formants.size, 1.0, 1.0);
317 		const integer maxnFormants = my formants.at [1] -> maxnFormants;
318 		if (numberOfTracks == 0)
319 			numberOfTracks = maxnFormants;
320 		for (integer itime = 1; itime <= my nx; itime ++) {
321 			for (integer iformant = 1; iformant <= my formants.size; iformant ++) {
322 				const Formant_Frame frame = & my formants.at [iformant] -> frames [itime];
323 				const integer currentNumberOfFormants = std::min (numberOfTracks, frame -> numberOfFormants);
324 				longdouble qsum = 0.0;
325 				for (integer itrack = 1; itrack <= currentNumberOfFormants; itrack ++)
326 					qsum += frame -> formant [itrack] . frequency / frame-> formant [itrack]. bandwidth;
327 				thy z [iformant] [itime] = ( currentNumberOfFormants > 0 ? (double) (qsum / currentNumberOfFormants) : 0.0 );
328 			}
329 		}
330 		return thee;
331 	} catch (MelderError) {
332 		Melder_throw (me, U": cannot calculate qsums.");
333 	}
334 }
335 
FormantPath_to_Matrix_transition(FormantPath me,integer numberOfTracks,bool maximumCosts)336 autoMatrix FormantPath_to_Matrix_transition (FormantPath me, integer numberOfTracks, bool maximumCosts) {
337 	try {
338 		autoMatrix thee = Matrix_create (my xmin, my xmax, my nx, my dx, my x1, 0.5, my formants.size + 0.5, my formants.size, 1.0, 1.0);
339 		const integer maxnFormants = my formants.at [1] -> maxnFormants;
340 		if (numberOfTracks == 0)
341 			numberOfTracks = maxnFormants;
342 		for (integer itime = 2; itime <= my nx; itime ++) {
343 			for (integer iformant = 1; iformant <= my formants.size; iformant++) {
344 				const Formant_Frame ffi = & my formants.at [iformant] -> frames [itime];
345 				const integer numberOfTracks_i = std::min (numberOfTracks, ffi -> numberOfFormants);
346 				MelderExtremaWithInit costs;
347 				for (integer jformant = 1; jformant <= my formants.size; jformant++) {
348 					const Formant_Frame ffj = & my formants.at [jformant] -> frames [itime - 1];
349 					const integer ntracks = std::min (ffj -> numberOfFormants, numberOfTracks_i);
350 					long double transitionCosts = 0.0;
351 					for (integer itrack = 1; itrack <= ntracks; itrack ++) {
352 						const double fi = ffi -> formant [itrack].frequency, fj = ffj -> formant [itrack] . frequency;
353 						const double dif = fabs (fi  - fj);
354 						const double sum = fi  + fj;
355 						const double bw = sqrt (ffi -> formant [itrack] . bandwidth * ffj -> formant [itrack] . bandwidth);
356 						double cost = bw * dif / sum;
357 						if (Melder_debug == -3)
358 							cost = fabs (NUMlog2 (fi / fj));
359 						transitionCosts += cost;
360 					}
361 					transitionCosts /= ntracks;
362 					costs.update ((double) transitionCosts);
363 				}
364 				thy z [iformant] [itime] = ( maximumCosts ? costs.max : costs.min );
365 			}
366 		}
367 		return thee;
368 	} catch (MelderError) {
369 		Melder_throw (me, U": cannot calculate transition costs.");
370 	}
371 }
372 
FormantPath_to_Matrix_stress(FormantPath me,double windowLength,constINTVEC const & parameters,double powerf)373 autoMatrix FormantPath_to_Matrix_stress (FormantPath me, double windowLength, constINTVEC const& parameters, double powerf) {
374 	try {
375 		const integer numberOfFormants = my formants.size;
376 		const integer maxnFormants = my formants.at [1] -> maxnFormants;
377 		Melder_require (parameters.size > 0 && parameters.size <= maxnFormants,
378 			U"The number of parameters should be between 1 and ", maxnFormants, U".");
379 		integer fromFormant = 1;
380 		const integer maximumNumberOfCoefficients = NUMmax (parameters);
381 		const integer numberOfDataPoints = (windowLength + 0.5 * my dx) / my dx;
382 		Melder_require (numberOfDataPoints >= maximumNumberOfCoefficients,
383 			U"The window length is too short for the number of coefficients you use in the stress determination (",
384 			maximumNumberOfCoefficients, U"). Either increase your window length or decrease the number of coefficents per track.");
385 		while (fromFormant <= parameters.size && parameters [fromFormant] <= 0)
386 			fromFormant ++;
387 		integer toFormant = parameters.size;
388 		while (toFormant > 0 && parameters [toFormant] <= 0)
389 			toFormant --;
390 		Melder_require (fromFormant <= toFormant,
391 			U"Not all parameter values should equal zero.");
392 		autoMatrix thee = Matrix_create (my xmin, my xmax, my nx, my dx, my x1, 0.5, numberOfFormants + 0.5, numberOfFormants, 1.0, 1.0);
393 		for (integer iformant = 1; iformant <= numberOfFormants; iformant ++) {
394 			const Formant formanti = (Formant) my formants . at [iformant];
395 			for (integer iframe = 1; iframe <= my nx; iframe ++) {
396 				const double time = my x1 + (iframe - 1) * my dx;
397 				const double startTime = time - 0.5 * windowLength;
398 				const double endTime = time + 0.5 * windowLength;
399 				autoFormantModeler fm = Formant_to_FormantModeler (formanti, startTime, endTime,  parameters);
400 				thy z [iformant] [iframe] = FormantModeler_getStress (fm.get(), fromFormant, toFormant, 0, powerf);
401 			}
402 		}
403 		return thee;
404 	} catch (MelderError) {
405 		Melder_throw (me, U": cannot create stress Matrix");
406 	}
407 }
408 
FormantPath_getStressOfFits(FormantPath me,double tmin,double tmax,integer fromFormant,integer toFormant,constINTVEC const & parameters,double powerf)409 autoVEC FormantPath_getStressOfFits (FormantPath me, double tmin, double tmax, integer fromFormant, integer toFormant, constINTVEC const& parameters, double powerf) {
410 	autoVEC stresses = raw_VEC (my formants.size);
411 	for (integer iformant = 1; iformant <= my formants.size; iformant ++) {
412 		const Formant formanti = (Formant) my formants.at [iformant];
413 		autoFormantModeler fm = Formant_to_FormantModeler (formanti, tmin, tmax,  parameters);
414 		stresses [iformant] = FormantModeler_getStress (fm.get(), fromFormant, toFormant, 0, powerf);
415 	}
416 	return stresses;
417 }
418 
FormantPath_downTo_Table_stresses(FormantPath me,double tmin,double tmax,constINTVEC const & parameters,double powerf,integer numberOfStressDecimals,bool includeIntervalTimes,integer numberOfTimeDecimals)419 autoTable FormantPath_downTo_Table_stresses (FormantPath me, double tmin, double tmax, constINTVEC const& parameters,
420 	double powerf, integer numberOfStressDecimals, bool includeIntervalTimes, integer numberOfTimeDecimals) {
421 	try {
422 		autoVEC stresses = FormantPath_getStressOfFits (me, tmin, tmax, 0, 0, parameters, powerf);
423 		const integer numberOfFormantObjects = my formants.size;
424 		const integer numberOfFormantsInFit = parameters.size;
425 		autoTable thee = Table_createWithoutColumnNames (numberOfFormantObjects, includeIntervalTimes + 1 + includeIntervalTimes + numberOfFormantsInFit + numberOfFormantsInFit - 1);
426 		integer icol = 0;
427 		if (includeIntervalTimes) {
428 			Table_setColumnLabel (thee.get(), 1, U"Start(s)");
429 			Table_setColumnLabel (thee.get(), 2, U"End(s)");
430 			for (integer irow = 1; irow <= numberOfFormantObjects; irow ++) {
431 				Table_setStringValue (thee.get(), irow, 1, Melder_fixed (tmin, numberOfTimeDecimals));
432 				Table_setStringValue (thee.get(), irow, 2, Melder_fixed (tmax, numberOfTimeDecimals));
433 			}
434 			icol = 2;
435 		}
436 		Table_setColumnLabel (thee.get(), ++ icol, U"Ceiling(Hz)");
437 		for (integer irow = 1; irow <= numberOfFormantObjects; irow ++)
438 				Table_setStringValue (thee.get(), irow, icol, Melder_fixed (my ceilings [irow], 1));
439 
440 		for (integer iformant = 1; iformant <= numberOfFormantsInFit; iformant ++) {
441 			Table_setColumnLabel (thee.get(), ++ icol, Melder_cat (U"Stress", iformant));
442 			autoVEC stresses_fi = FormantPath_getStressOfFits (me, tmin, tmax, iformant, iformant, parameters, powerf);
443 			for (integer irow = 1; irow <= numberOfFormantObjects; irow ++)
444 				Table_setStringValue (thee.get(), irow, icol, Melder_fixed (stresses_fi [irow], numberOfStressDecimals));
445 		}
446 		for (integer iformant = 2; iformant <= numberOfFormantsInFit; iformant ++) {
447 			Table_setColumnLabel (thee.get(), ++ icol, Melder_cat (U"Stress", 1, iformant));
448 			autoVEC stresses_fij = FormantPath_getStressOfFits (me, tmin, tmax, 1, iformant, parameters, powerf);
449 			for (integer irow = 1; irow <= numberOfFormantObjects; irow ++)
450 				Table_setStringValue (thee.get(), irow, icol, Melder_fixed (stresses_fij [irow], numberOfStressDecimals));
451 		}
452 		return thee;
453 	} catch (MelderError) {
454 		Melder_throw (me, U": cannot create Table with stresses of fit.");
455 	}
456 }
457 
FormantPath_downTo_Table_optimalInterval(FormantPath me,double tmin,double tmax,constINTVEC const & parameters,double powerf,bool includeFrameNumber,bool includeTime,integer numberOfTimeDecimals,bool includeIntensity,integer numberOfIntensityDecimals,bool includeNumberOfFormants,integer numberOfFrequencyDecimals,bool includeBandwidths,bool includeOptimumCeiling,bool includeMinimumStress)458 autoTable FormantPath_downTo_Table_optimalInterval (FormantPath me, double tmin, double tmax,
459 	constINTVEC const& parameters, double powerf, bool includeFrameNumber, bool includeTime, integer numberOfTimeDecimals,
460 	bool includeIntensity, integer numberOfIntensityDecimals, bool includeNumberOfFormants, integer numberOfFrequencyDecimals,
461 	bool includeBandwidths, bool includeOptimumCeiling, bool includeMinimumStress)
462 {
463 	try {
464 		autoVEC stresses = FormantPath_getStressOfFits (me, tmin, tmax, 0, 0, parameters, powerf);
465 		const integer minPos = NUMminPos (stresses.get());
466 		const integer minPosFallBack = ( minPos != 0 ? minPos : stresses.size / 2 );
467 		const Formant formant = (Formant) my formants.at [minPosFallBack];
468 		integer ifmin, ifmax;
469 		Sampled_getWindowSamples (formant, tmin, tmax, & ifmin, & ifmax);
470 		autoFormant thee = Formant_extractPart (formant, tmin, tmax);
471 		autoTable him = Formant_downto_Table (thee.get(), includeFrameNumber, includeTime, numberOfTimeDecimals,
472 			includeIntensity, numberOfIntensityDecimals, includeNumberOfFormants, numberOfFrequencyDecimals, includeBandwidths);
473 		if (includeFrameNumber) {
474 			/*
475 				Correct frame number, it has to start at ifmin instead of 1
476 			*/
477 			for (integer irow = 1; irow <= his rows.size; irow ++)
478 				Table_setNumericValue (him.get(), irow, 1, ifmin + irow - 1);
479 		}
480 		if (includeOptimumCeiling) {
481 			Table_appendColumn (him.get(), U"Ceiling(Hz)");
482 			for (integer irow = 1; irow <= his rows.size; irow ++)
483 				Table_setStringValue (him.get(), irow, his numberOfColumns, Melder_fixed (my ceilings [minPosFallBack], 1));
484 		}
485 		if (includeMinimumStress) {
486 			Table_appendColumn (him.get(), U"Stress");
487 			for (integer irow = 1; irow <= his rows.size; irow ++)
488 				Table_setStringValue (him.get(), irow, his numberOfColumns, Melder_fixed (stresses [minPosFallBack], 2));
489 		}
490 		return him;
491 	} catch (MelderError) {
492 		Melder_throw (me, U"could not list as Table.");
493 	}
494 }
495 
Formant_speckles_inside(Formant me,Graphics g,double tmin,double tmax,double fmin,double fmax,integer fromFormant,integer toFormant,double suppress_dB,bool drawBandWidths,MelderColour oddNumberedFormants,MelderColour evenNumberedFormants)496 static void Formant_speckles_inside (Formant me, Graphics g, double tmin, double tmax, double fmin, double fmax,
497 	integer fromFormant, integer toFormant, double suppress_dB, bool drawBandWidths, MelderColour oddNumberedFormants, MelderColour evenNumberedFormants)
498 {
499 	double maximumIntensity = 0.0, minimumIntensity;
500 	Function_unidirectionalAutowindow (me, & tmin, & tmax);
501 	integer itmin, itmax;
502 	if (! Sampled_getWindowSamples (me, tmin, tmax, & itmin, & itmax))
503 		return;
504 	if (fromFormant == 0 && toFormant == 0) {
505 		fromFormant = 1;
506 		toFormant = my maxnFormants;
507 	}
508 	Graphics_setWindow (g, tmin, tmax, fmin, fmax);
509 
510 	for (integer iframe = itmin; iframe <= itmax; iframe ++) {
511 		const Formant_Frame frame = & my frames [iframe];
512 		if (frame -> intensity > maximumIntensity)
513 			maximumIntensity = frame -> intensity;
514 	}
515 	if (maximumIntensity == 0.0 || suppress_dB <= 0.0)
516 		minimumIntensity = 0.0;   // ignore
517 	else
518 		minimumIntensity = maximumIntensity / pow (10.0, suppress_dB / 10.0);
519 
520 	for (integer iframe = itmin; iframe <= itmax; iframe ++) {
521 		const Formant_Frame frame = & my frames [iframe];
522 		const double x = Sampled_indexToX (me, iframe);
523 		if (frame -> intensity < minimumIntensity)
524 			continue;
525 		/*
526 			Higher formants in general have larger bandwidths. Draw them first to show lower formants clearer.
527 		*/
528 		for (integer iformant = std::min (frame -> numberOfFormants, toFormant); iformant >= fromFormant; iformant --) {
529 			const double frequency = frame -> formant [iformant]. frequency;
530 			Graphics_setColour (g, iformant % 2 == 1 ? oddNumberedFormants : evenNumberedFormants );
531 			if (frequency >= fmin && frequency <= fmax) {
532 				Graphics_speckle (g, x, frequency);
533 				if (drawBandWidths) {
534 					const double bandwidth = frame -> formant [iformant]. bandwidth;
535 					const double upper = std::min (frequency + 0.5 * bandwidth, fmax);
536 					const double lower = std::max (frequency - 0.5 * bandwidth, fmin);
537 					Graphics_line (g, x, upper, x, lower);
538 				}
539 			}
540 		}
541 	}
542 }
543 
FormantPath_drawAsGrid_inside(FormantPath me,Graphics g,double tmin,double tmax,double fmax,integer fromFormant,integer toFormant,bool showBandwidths,MelderColour oddNumberedFormants,MelderColour evenNumberedFormants,integer nrow,integer ncol,double spaceBetweenFraction_x,double spaceBetweenFraction_y,double yGridLineEvery_Hz,double xCursor,double yCursor,MelderColour selectedCeilingsColour,constINTVEC const & parameters,bool markCandidatesWithinPath,bool showStress,double powerf,bool showEstimatedModels,bool garnish)544 void FormantPath_drawAsGrid_inside (FormantPath me, Graphics g, double tmin, double tmax, double fmax,
545 	integer fromFormant, integer toFormant, bool showBandwidths, MelderColour oddNumberedFormants, MelderColour evenNumberedFormants,
546 	integer nrow, integer ncol, double spaceBetweenFraction_x, double spaceBetweenFraction_y, double yGridLineEvery_Hz,
547 	double xCursor, double yCursor, MelderColour selectedCeilingsColour, constINTVEC const& parameters,
548 	bool markCandidatesWithinPath, bool showStress, double powerf, bool showEstimatedModels, bool garnish)
549 {
550 	constexpr double fmin = 0.0;
551 	if (nrow <= 0 || ncol <= 0)
552 		FormantPath_getGridDimensions (me, & nrow, & ncol);
553 	double x1NDC, x2NDC, y1NDC, y2NDC;
554 	Graphics_inqViewport (g, & x1NDC, & x2NDC, & y1NDC, & y2NDC);
555 	const double fontSize_old = Graphics_inqFontSize (g), newFontSize = 8.0;
556 	const double vp_width = x2NDC - x1NDC, vp_height = y2NDC - y1NDC;
557 	const double vpi_width = vp_width / (ncol + (ncol - 1) * spaceBetweenFraction_x);
558 	const double vpi_height = vp_height / (nrow + (nrow - 1) * spaceBetweenFraction_y);
559 	autoIntervalTier intervalTier = FormantPath_to_IntervalTier (me, tmin, tmax);
560 	integer itmin, itmax;
561 	const integer numberOfSamples = Sampled_getWindowSamples (me, tmin, tmax, & itmin, & itmax);
562 
563 	for (integer iformant = 1; iformant <= my formants.size; iformant ++) {
564 		const integer irow = 1 + (iformant - 1) / ncol; // left-to-right + top-to-bottom
565 		const integer icol = 1 + (iformant - 1) % ncol;
566 		const double vpi_x1 = x1NDC + (icol - 1) * vpi_width * (1.0 + spaceBetweenFraction_x);
567 		const double vpi_x2 = vpi_x1 + vpi_width;
568 		const double vpi_y2 = y2NDC - (irow - 1) * vpi_height * (1.0 + spaceBetweenFraction_y);
569 		const double vpi_y1 = vpi_y2 - vpi_height;
570 		const Formant formant = my formants.at [iformant];
571 		autoFormantModeler fm;
572 		if (numberOfSamples > 0)
573 			fm = Formant_to_FormantModeler (formant, tmin, tmax, parameters);
574 		Graphics_setViewport (g, vpi_x1, vpi_x2, vpi_y1, vpi_y2);
575 		Graphics_setWindow (g, tmin, tmax, fmin, fmax);
576 		if (garnish && markCandidatesWithinPath) {
577 			for (integer interval = 1; interval <= intervalTier -> intervals.size; interval ++) {
578 				TextInterval textInterval = intervalTier -> intervals.at [interval];
579 				const integer candidate = ( textInterval -> text.get() ? Melder_atoi (textInterval -> text.get()) : 0);
580 				if (candidate == iformant) {
581 					MelderColour colourCopy = Graphics_inqColour (g);
582 					Graphics_setColour (g, selectedCeilingsColour);
583 					Graphics_fillRectangle (g, textInterval -> xmin, textInterval -> xmax, 0, fmax);
584 					Graphics_setColour (g, colourCopy);
585 				}
586 			}
587 		}
588 		Formant_speckles_inside (formant, g, tmin, tmax, fmin, fmax, fromFormant, toFormant, 100.0, showBandwidths, oddNumberedFormants, evenNumberedFormants);
589 		if (showEstimatedModels && numberOfSamples > 0)
590 			FormantModeler_drawModel_inside (fm.get(), g, tmin, tmax, fmax, fromFormant, toFormant, oddNumberedFormants, evenNumberedFormants, 100_integer);
591 		Graphics_setColour (g, Melder_BLACK);
592 		if (garnish)
593 			Graphics_rectangle (g, tmin, tmax, fmin, fmax);
594 
595 		Graphics_setLineType (g, Graphics_DRAWN);
596 		Graphics_setLineWidth (g, 1.0);
597 		/*
598 			Mark ceiling & stress
599 		*/
600 		autoMelderString info;
601 		const double tLeftPos = tmin - 0.01 * (tmax - tmin), tRightPos = tmax + 0.01 * (tmax - tmin);
602 		if (garnish) {
603 			if (showStress && numberOfSamples > 0) {
604 				const double stress = FormantModeler_getStress (fm.get(), fromFormant, toFormant, 0, powerf);
605 				MelderString_append (& info, U"Fit=", Melder_fixed (stress, 2));
606 				Graphics_setTextAlignment (g, kGraphics_horizontalAlignment::RIGHT, Graphics_BOTTOM);
607 				Graphics_text (g, tRightPos, fmax, info.string);
608 			}
609 			MelderString_empty (& info);
610 			Graphics_setTextAlignment (g, kGraphics_horizontalAlignment::LEFT, Graphics_BOTTOM);
611 			MelderString_append (& info, U"Ceiling=", Melder_fixed (my ceilings [iformant], 0), U" Hz");
612 			Graphics_text (g, tLeftPos, fmax, info.string);
613 		}
614 		Graphics_setTextAlignment (g, kGraphics_horizontalAlignment::CENTRE, Graphics_HALF);
615 		if (garnish) {
616 			auto getXtick = [] (Graphics gg, double fontSize) {
617 				const double margin = 2.8 * fontSize * gg -> resolution / 72.0;
618 				const double wDC = (gg -> d_x2DC - gg -> d_x1DC) / (gg -> d_x2wNDC - gg -> d_x1wNDC) * (gg -> d_x2NDC - gg -> d_x1NDC);
619 				double dx = 1.5 * margin / wDC;
620 				double xTick = 0.06 * dx;
621 				if (dx > 0.4) dx = 0.4;
622 				return xTick /= 1.0 - 2.0 * dx;
623 			};
624 			auto getYtick = [] (Graphics gg, double fontSize) {
625 				const double margin = 2.8 * fontSize * gg -> resolution / 72.0;
626 				const double hDC = integer_abs (gg->d_y2DC - gg->d_y1DC) / (gg->d_y2wNDC - gg->d_y1wNDC) * (gg->d_y2NDC - gg-> d_y1NDC);
627 				double dy = margin / hDC;
628 				double yTick = 0.09 * dy;
629 				if (dy > 0.4) dy = 0.4;
630 				yTick /= 1.0 - 2.0 * dy;
631 				return yTick;
632 			};
633 			const double xTick = (double) getXtick (g, newFontSize) * (tmax - tmin);
634 			const double yTick = (double) getYtick (g, newFontSize) * (fmax - fmin);
635 			if (irow == nrow) {
636 				MelderString_empty (& info);
637 				MelderString_append (& info, Melder_fixed (tmin, 3), U" s");
638 				Graphics_setTextAlignment (g, kGraphics_horizontalAlignment::LEFT, Graphics_TOP);
639 				Graphics_line (g, tmin, fmin, tmin, fmin - yTick);
640 				Graphics_text (g, tmin , fmin - yTick, info.string);
641 				MelderString_empty (& info);
642 				MelderString_append (& info, Melder_fixed (tmax, 3), U" s");
643 				Graphics_line (g, tmax, fmin, tmax, fmin - yTick);
644 				Graphics_setTextAlignment (g, kGraphics_horizontalAlignment::RIGHT, Graphics_TOP);
645 				Graphics_text (g, tmax, fmin - yTick, info.string);
646 			}
647 			if (icol == 1) {
648 				MelderString_empty (& info);
649 				MelderString_append (& info, Melder_iround (fmin), U" Hz");
650 				Graphics_line (g, tmin - xTick, fmin, tmin, fmin);
651 				Graphics_setTextAlignment (g, kGraphics_horizontalAlignment::RIGHT, Graphics_HALF);
652 				Graphics_text (g, tmin - xTick, fmin, info.string);
653 				MelderString_empty (& info);
654 				MelderString_append (& info, Melder_iround (fmax), U" Hz");
655 				Graphics_text (g, tmin - xTick, fmax, info.string);
656 
657 			}
658 			double yGridLine_Hz = yGridLineEvery_Hz;
659 			Graphics_setLineType (g, Graphics_DOTTED);
660 			while (yGridLine_Hz < 0.95 * fmax) {
661 				Graphics_line (g, tmin, yGridLine_Hz, tmax, yGridLine_Hz);
662 				yGridLine_Hz += yGridLineEvery_Hz;
663 			}
664 			/*
665 				Cursors
666 			*/
667 			Graphics_setColour (g, Melder_RED);
668 			Graphics_setLineType (g, Graphics_DASHED);
669 			if (xCursor > tmin && xCursor <= tmax)
670 				Graphics_line (g, xCursor, 0.0, xCursor, fmax);
671 			if (yCursor > 0.0 && yCursor < fmax)
672 				Graphics_line (g, tmin, yCursor, tmax, yCursor);
673 			Graphics_setColour (g, Melder_BLACK);
674 			Graphics_setLineType (g, Graphics_DRAWN);
675 		}
676 	}
677 	Graphics_setFontSize (g, fontSize_old);
678 	Graphics_setViewport (g, x1NDC, x2NDC, y1NDC, y2NDC);
679 
680 }
681 
FormantPath_drawAsGrid(FormantPath me,Graphics g,double tmin,double tmax,double fmax,integer fromFormant,integer toFormant,bool showBandwidths,MelderColour oddNumberedFormants,MelderColour evenNumberedFormants,integer nrow,integer ncol,double spaceBetweenFraction_x,double spaceBetweenFraction_y,double yGridLineEvery_Hz,double xCursor,double yCursor,MelderColour selected,constINTVEC const & parameters,bool markCandidatesWithinPath,bool showStress,double powerf,bool showEstimatedModels,bool garnish)682 void FormantPath_drawAsGrid (FormantPath me, Graphics g, double tmin, double tmax, double fmax,
683 	integer fromFormant, integer toFormant, bool showBandwidths, MelderColour oddNumberedFormants, MelderColour evenNumberedFormants,
684 	integer nrow, integer ncol, double spaceBetweenFraction_x, double spaceBetweenFraction_y, double yGridLineEvery_Hz,
685 	double xCursor, double yCursor, MelderColour selected, constINTVEC const& parameters,
686 	bool markCandidatesWithinPath, bool showStress, double powerf, bool showEstimatedModels, bool garnish)
687 {
688 	Function_bidirectionalAutowindow (me, & tmin, & tmax);
689 	Graphics_setInner (g);
690 	FormantPath_drawAsGrid_inside (me, g, tmin, tmax, fmax, fromFormant, toFormant, showBandwidths, oddNumberedFormants, evenNumberedFormants,
691 		nrow, ncol, spaceBetweenFraction_x, spaceBetweenFraction_y, yGridLineEvery_Hz, xCursor, yCursor, selected, parameters,
692 		markCandidatesWithinPath, showStress, powerf, showEstimatedModels, garnish
693 	);
694 	Graphics_unsetInner (g);
695 }
696 
697 /* End of file FormantPath.cpp */
698