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