Audacity 3.2.0
ParsedNumericConverterFormatter.cpp
Go to the documentation of this file.
1/* SPDX-License-Identifier: GPL-2.0-or-later */
2/**********************************************************************
3
4 Audacity: A Digital Audio Editor
5
6 @file ParsedNumericConverterFormatter.cpp
7
8 Dmitry Vedenko split from NumericConverter.cpp
9
10 **********************************************************************/
13
14#include "SampleCount.h"
17
18#include "Project.h"
19#include "ProjectRate.h"
20
21#include <cmath>
22
23namespace
24{
151struct FieldConfig final
152{
153 bool frac; // is it a fractional field
154 int base; // divide by this (multiply, after decimal point)
155 // Code in the parser converts range to `long`
156 long range; // then take modulo this
157};
158
161 public PrefsListener
162{
163public:
165 NumericConverterType type, const TranslatableString& untranslatedFormat, const FormatterContext& context)
166 : mContext { context }
167 , mType { type }
168 , mFormat { untranslatedFormat.Translation() }
169 , mUntranslatedFormat { untranslatedFormat }
170 {
171 UpdateFormat();
172
173 if (IsTimeRelatedFormat())
174 {
175 auto project = mContext.GetProject();
176
177 if (project != nullptr)
178 {
179 // We need a non const object to subscribe...
180 mProjectRateChangedSubscription =
181 const_cast<ProjectRate&>(ProjectRate::Get(*project))
182 .Subscribe([this](const auto&) { UpdateFormat(); });
183 }
184 }
185 }
186
188 {
189 return mType == NumericConverterType_TIME() ||
191 }
192
193 void
195 {
196 mPrefix.clear();
197 mFields.clear();
198 mDigits.clear();
199 mFieldConfigs.clear();
200
201 mScalingFactor = 1.0;
202
203 // We will change inFrac to true when we hit our first decimal point.
204 bool inFrac = false;
205 int fracMult = 1;
206 int numWholeFields = 0;
207 int numFracFields = 0;
208 wxString numStr;
209 wxString delimStr;
210 unsigned int i;
211
212 mNtscDrop = false;
213 for (i = 0; i < mFormat.length(); i++)
214 {
215 bool handleDelim = false;
216 bool handleNum = false;
217
218 if (mFormat[i] == '|')
219 {
220 wxString remainder = mFormat.Right(mFormat.length() - i - 1);
221 // For languages which use , as a separator.
222 remainder.Replace(wxT(","), wxT("."));
223
224 mScalingFactorIsSamples = remainder == wxT("#");
225
226 if (mScalingFactorIsSamples)
227 {
228 mScalingFactor = mSampleRate;
229 }
230 else if (remainder == wxT("N"))
231 {
232 mNtscDrop = true;
233 }
234 else
235 // Use the C locale here for string to number.
236 // Translations are often incomplete.
237 // We can't rely on the correct ',' or '.' in the
238 // translation, so we work based on '.' for decimal point.
239 remainder.ToCDouble(&mScalingFactor);
240 i = mFormat.length() - 1; // force break out of loop
241 if (!delimStr.empty())
242 handleDelim = true;
243 if (!numStr.empty())
244 handleNum = true;
245 }
246 else if (
247 (mFormat[i] >= '0' && mFormat[i] <= '9') ||
248 mFormat[i] == wxT('*') || mFormat[i] == wxT('#'))
249 {
250 numStr += mFormat[i];
251 if (!delimStr.empty())
252 handleDelim = true;
253 }
254 else
255 {
256 delimStr += mFormat[i];
257 if (!numStr.empty())
258 handleNum = true;
259 }
260
261 if (i == mFormat.length() - 1)
262 {
263 if (!numStr.empty())
264 handleNum = true;
265 if (!delimStr.empty())
266 handleDelim = true;
267 }
268
269 if (handleNum)
270 {
271 bool zeropad = false;
272 long range = 0;
273
274 if (numStr.Right(1) == wxT("#"))
275 range = static_cast<long int>(mSampleRate);
276 else if (numStr.Right(1) != wxT("*"))
277 {
278 numStr.ToLong(&range);
279 }
280 if (numStr.GetChar(0) == '0' && numStr.length() > 1)
281 zeropad = true;
282
283 // Hack: always zeropad
284 zeropad = true;
285
286 if (inFrac)
287 {
288 int base = fracMult * range;
289 mFieldConfigs.push_back({ inFrac, base, range });
290 mFields.push_back(NumericField::ForRange(range, zeropad));
291 fracMult *= range;
292 numFracFields++;
293 }
294 else
295 {
296 unsigned int j;
297 for (j = 0; j < mFields.size(); j++)
298 mFieldConfigs[j].base *= range;
299 mFieldConfigs.push_back({ inFrac, 1, range });
300 mFields.push_back(NumericField::ForRange(range, zeropad));
301 numWholeFields++;
302 }
303 numStr = wxT("");
304 }
305
306 if (handleDelim)
307 {
308 bool goToFrac = false;
309
310 if (!inFrac)
311 {
312 wxChar delim = delimStr[delimStr.length() - 1];
313 if (delim == '<' || delim == '>')
314 {
315 goToFrac = true;
316 if (delimStr.length() > 1)
317 delimStr = delimStr.BeforeLast(delim);
318 }
319 }
320
321 if (inFrac)
322 {
323 if (numFracFields == 0)
324 {
325 // Should never happen
326 return;
327 }
328 if (handleNum && numFracFields > 1)
329 mFields[mFields.size() - 2].label = delimStr;
330 else
331 mFields[mFields.size() - 1].label = delimStr;
332 }
333 else
334 {
335 if (numWholeFields == 0)
336 mPrefix = delimStr;
337 else
338 {
339 delimStr.Replace(wxT("<"), wxT(","));
340 delimStr.Replace(wxT(">"), wxT("."));
341 mFields[numWholeFields - 1].label = delimStr;
342 }
343 }
344
345 if (goToFrac)
346 inFrac = true;
347 delimStr = wxT("");
348 }
349 }
350
351 size_t pos = 0;
352
353 pos += mPrefix.length();
354
355 for (i = 0; i < mFields.size(); i++)
356 {
357 mFields[i].pos = pos;
358
359 for (size_t j = 0; j < mFields[i].digits; j++)
360 {
361 mDigits.push_back(DigitInfo { i, j, pos });
362 pos++;
363 }
364
365 pos += mFields[i].label.length();
366 }
367
368 // This Publish will happen from the
369 // constructor as well, despite it is not
370 // possible to catch it there
371 Publish({});
372 }
373
375 {
376 const auto newSampleRate = mContext.GetSampleRate();
377
378 const bool sampleRateChanged = newSampleRate != mSampleRate;
379
380 mSampleRate = newSampleRate;
381
382 if (mFields.empty() || (sampleRateChanged && mScalingFactorIsSamples))
383 ParseFormatString();
384 }
385
387 double rawValue, bool nearest) const override
388 {
389 ConversionResult result;
390
391 if (IsTimeRelatedFormat() && mContext.HasSampleRate())
392 rawValue = floor(rawValue * mSampleRate + (nearest ? 0.5f : 0.0f)) /
393 mSampleRate; // put on a sample
394 double theValue = rawValue * mScalingFactor
395 // PRL: what WAS this .000001 for? Nobody could explain.
396 // + .000001
397 ;
398
399 sampleCount t_int;
400 bool round = true;
401 // We round on the last field. If we have a fractional field we round
402 // using it. Otherwise we round to nearest integer.
403 for (size_t i = 0; i < mFields.size(); i++)
404 {
405 if (mFieldConfigs[i].frac)
406 round = false;
407 }
408 if (theValue < 0)
409 t_int = -1;
410 else if (round)
411 t_int = sampleCount(theValue + (nearest ? 0.5f : 0.0f));
412 else
413 {
414 wxASSERT(mFieldConfigs.back().frac);
415 theValue += (nearest ? 0.5f : 0.0f) / mFieldConfigs.back().base;
416 t_int = sampleCount(theValue);
417 }
418 double t_frac;
419 if (theValue < 0)
420 t_frac = -1;
421 else
422 t_frac = (theValue - t_int.as_double());
423
424 int tenMins;
425 int mins;
426 int addMins;
427 int secs;
428 int frames;
429
430 result.valueString = mPrefix;
431
432 if (mNtscDrop && theValue >= 0)
433 {
434 frames = (int)(theValue * 30. / 1.001 + (nearest ? 0.5f : 0.0f));
435 tenMins = frames / 17982;
436 frames -= tenMins * 17982;
437 mins = tenMins * 10;
438 if (frames >= 1800)
439 {
440 frames -= 1800;
441 mins++;
442 addMins = frames / 1798;
443 frames -= addMins * 1798;
444 mins += addMins;
445 secs = frames / 30;
446 frames -= secs * 30;
447 frames += 2;
448 if (frames >= 30)
449 {
450 secs++;
451 frames -= 30;
452 }
453 }
454 else
455 {
456 secs = frames / 30;
457 frames -= secs * 30;
458 }
459 t_int = mins * 60 + secs;
460 t_frac = frames / 30.;
461 }
462
463 for (size_t i = 0; i < mFields.size(); i++)
464 {
465 long long value = -1;
466
467 if (mFieldConfigs[i].frac)
468 {
469 // JKC: This old code looks bogus to me.
470 // The rounding is not propagating to earlier fields in the frac
471 // case.
472 // value = (int)(t_frac * mFields[i].base + 0.5); // +0.5 as
473 // rounding required
474 // I did the rounding earlier.
475 if (t_frac >= 0)
476 value = t_frac * mFieldConfigs[i].base;
477 // JKC: TODO: Find out what the range is supposed to do.
478 // It looks bogus too.
479 // if (mFields[i].range > 0)
480 // value = value % mFields[i].range;
481 }
482 else
483 {
484 if (t_int >= 0)
485 {
486 value = t_int.as_long_long() / mFieldConfigs[i].base;
487 if (mFieldConfigs[i].range > 0)
488 value = value % mFieldConfigs[i].range;
489 }
490 }
491
492 wxString field;
493
494 if (value < 0)
495 {
496 for (int ii = 0; ii < mFields[i].digits; ++ii)
497 field += wxT("-");
498 }
499 else
500 field = wxString::Format(mFields[i].formatStr, (int)value);
501
502 result.fieldValueStrings.push_back(field);
503
504 result.valueString += field;
505 result.valueString += mFields[i].label;
506 }
507
508 return result;
509 }
510
511 std::optional<double> StringToValue(
512 const wxString& valueString) const override
513 {
514 unsigned int i;
515 double t = 0.0;
516
517 if (
518 mFields.size() > 0 &&
519 valueString.Mid(mFields[0].pos, 1) == wxChar('-'))
520 return std::nullopt;
521
522 for (i = 0; i < mFields.size(); i++)
523 {
524 const auto pos = mFields[i].pos;
525 const auto digits = mFields[i].digits;
526
527 if (pos >= valueString.size() || pos + digits > valueString.size())
528 return std::nullopt;
529
530 long val;
531
532 const auto fieldStringValue =
533 valueString.Mid(mFields[i].pos, mFields[i].digits);
534
535 if (!fieldStringValue.ToLong(&val))
536 return std::nullopt;
537
538 if (mFieldConfigs[i].frac)
539 t += (val / (double)mFieldConfigs[i].base);
540 else
541 t += (val * (double)mFieldConfigs[i].base);
542 }
543
544 t /= mScalingFactor;
545
546 if (mNtscDrop)
547 {
548 int t_int = (int)(t + .000000001);
549 double t_frac = (t - t_int);
550 int tenMins = t_int / 600;
551 double frames = tenMins * 17982;
552 t_int -= tenMins * 600;
553 int mins = t_int / 60;
554 int addMins = 0;
555 if (mins > 0)
556 {
557 frames += 1800;
558 addMins = mins - 1;
559 }
560 frames += addMins * 1798;
561 t_int -= mins * 60;
562 if (mins == 0) // first min of a block of 10, don't drop frames 0 and 1
563 frames += t_int * 30 + t_frac * 30.;
564 else
565 { // drop frames 0 and 1 of first seconds of these minutes
566 if (t_int > 0)
567 frames += 28 + (t_int - 1) * 30 + t_frac * 30.;
568 else
569 frames += t_frac * 30. - 2.;
570 }
571 t = frames * 1.001 / 30.;
572 }
573
574 return t;
575 }
576
577 double SingleStep(double value, int digitIndex, bool upwards) const override
578 {
579 const auto dir = upwards ? 1 : -1;
580 for (size_t i = 0; i < mFields.size(); i++)
581 {
582 if (
583 (mDigits[digitIndex].pos >= mFields[i].pos) &&
584 (mDigits[digitIndex].pos < mFields[i].pos + mFields[i].digits))
585 { // it's this field
586 if (value < 0)
587 value = 0;
588
589 value *= mScalingFactor;
590
591 const double mult = pow(
592 10., mFields[i].digits -
593 (mDigits[digitIndex].pos - mFields[i].pos) - 1);
594
595 if (mFieldConfigs[i].frac)
596 {
597 value += ((mult / (double)mFieldConfigs[i].base) * dir);
598 }
599 else
600 {
601 value += ((mult * (double)mFieldConfigs[i].base) * dir);
602 }
603
604 if (mNtscDrop)
605 {
606 if ((value - (int)value) * 30 < 2)
607 {
608 if ((((int)value) % 60 == 0) && (((int)value) % 600 != 0))
609 {
610 value = (int)value + (dir > 0 ? 2. : -1.) / 30.;
611 }
612 }
613 }
614
615 if (value < 0.)
616 {
617 value = 0.;
618 }
619
620 value /= mScalingFactor;
621
622 if (mNtscDrop)
623 {
624 mNtscDrop = false;
625 auto result = ValueToString(value, false);
626 mNtscDrop = true;
627 return *StringToValue(result.valueString);
628 }
629
630 return value;
631 }
632 }
633
634 return value;
635 }
636
637 void UpdatePrefs() override
638 {
639 auto newFormat = mUntranslatedFormat.Translation();
640
641 if (mFormat == newFormat)
642 return;
643
644 mFormat = newFormat;
645 ParseFormatString();
646 }
647private:
650 wxString mFormat;
652
653 std::vector<FieldConfig> mFieldConfigs;
654
656 double mSampleRate { 1.0 };
657
659
660 bool mScalingFactorIsSamples { false };
661
662 mutable bool mNtscDrop;
663};
664
665//
666// ----------------------------------------------------------------------------
667// BuiltinFormatString Struct
668// ----------------------------------------------------------------------------
669//
670struct FormatStrings final
671{
673 // How to name the fraction of the unit; not necessary for time formats
674 // or when the format string has no decimal point
676
678 const TranslatableString& format = {},
679 const TranslatableString& fraction = {})
680 : formatStr { format }
681 , fraction { fraction }
682 {
683 }
684
685 friend bool operator==(const FormatStrings& x, const FormatStrings& y)
686 {
687 return x.formatStr == y.formatStr && x.fraction == y.fraction;
688 }
689 friend bool operator!=(const FormatStrings& x, const FormatStrings& y)
690 {
691 return !(x == y);
692 }
693};
698{
701
702 friend inline bool
704 {
705 return a.name == b.name;
706 }
707};
713 {
715 /* i18n-hint: Format string for displaying time in seconds. Change the comma
716 * in the middle to the 1000s separator for your locale, and the 'seconds'
717 * on the end to the word for seconds. Don't change the numbers. */
718 XO("01000,01000 seconds")
719 },
720
721 {
722 /* i18n-hint: Name of time display format that shows time in seconds
723 * and milliseconds (1/1000 second) */
724 { XO("seconds + milliseconds") },
725 /* i18n-hint: Format string for displaying time in seconds and milliseconds
726 * as fractional seconds. Change the comma in the middle to the 1000s separator
727 * for your locale, and the 'seconds' on the end to the word for seconds.
728 * Don't change the numbers. The decimal separator is specified using '<' if
729 * your languages uses a ',' or to '>' if your language uses a '.'. */
730 { XO("01000,01000>01000 seconds"),
731 XO("milliseconds") }
732 },
733
734 {
736 /* i18n-hint: Format string for displaying time in hours, minutes and
737 * seconds. Change the 'h' to the abbreviation for hours, 'm' to the
738 * abbreviation for minutes and 's' to the abbreviation for seconds. Don't
739 * change the numbers unless there aren't 60 seconds in a minute in your
740 * locale */
741 XO("0100 h 060 m 060 s")
742 },
743
744 {
745 /* i18n-hint: Name of time display format that shows time in days, hours,
746 * minutes and seconds */
747 { XO("dd:hh:mm:ss") },
748 /* i18n-hint: Format string for displaying time in days, hours, minutes and
749 * seconds. Change the 'days' to the word for days, 'h' to the abbreviation
750 * for hours, 'm' to the abbreviation for minutes and 's' to the
751 * abbreviation for seconds. Don't change the numbers unless there aren't
752 * 24 hours in a day in your locale */
753 XO("0100 days 024 h 060 m 060 s")
754 },
755
756 {
758 /* i18n-hint: Format string for displaying time in hours, minutes, seconds
759 * and hundredths of a second. Change the 'h' to the abbreviation for hours,
760 * 'm' to the abbreviation for minutes and 's' to the abbreviation for seconds
761 * (the hundredths are shown as decimal seconds). Don't change the numbers
762 * unless there aren't 60 minutes in an hour in your locale.
763 * The decimal separator is specified using '<' if your language uses a ',' or
764 * to '>' if your language uses a '.'. */
765 { XO("0100 h 060 m 060>0100 s"),
766 XO("centiseconds") }
767 },
768
769 {
771 /* i18n-hint: Format string for displaying time in hours, minutes, seconds
772 * and milliseconds. Change the 'h' to the abbreviation for hours, 'm' to the
773 * abbreviation for minutes and 's' to the abbreviation for seconds (the
774 * milliseconds are shown as decimal seconds) . Don't change the numbers
775 * unless there aren't 60 minutes in an hour in your locale.
776 * The decimal separator is specified using '<' if your language uses a ',' or
777 * to '>' if your language uses a '.'. */
778 { XO("0100 h 060 m 060>01000 s"),
779 XO("milliseconds") }
780 },
781
782 {
784 /* i18n-hint: Format string for displaying time in hours, minutes, seconds
785 * and samples. Change the 'h' to the abbreviation for hours, 'm' to the
786 * abbreviation for minutes, 's' to the abbreviation for seconds and
787 * translate samples . Don't change the numbers
788 * unless there aren't 60 seconds in a minute in your locale.
789 * The decimal separator is specified using '<' if your language uses a ',' or
790 * to '>' if your language uses a '.'. */
791 XO("0100 h 060 m 060 s+># samples")
792 },
793
794 {
795 /* i18n-hint: Name of time display format that shows time in samples (at the
796 * current project sample rate). For example the number of a sample at 1
797 * second into a recording at 44.1KHz would be 44,100.
798 */
799 { XO("samples") },
800 /* i18n-hint: Format string for displaying time in samples (lots of samples).
801 * Change the ',' to the 1000s separator for your locale, and translate
802 * samples. If 1000s aren't a base multiple for your number system, then you
803 * can change the numbers to an appropriate one, and put a 0 on the front */
804 XO("01000,01000,01000 samples|#")
805 },
806
807 {
808 /* i18n-hint: Name of time display format that shows time in hours, minutes,
809 * seconds and frames at 24 frames per second (commonly used for films) */
810 { XO("hh:mm:ss + film frames (24 fps)") },
811 /* i18n-hint: Format string for displaying time in hours, minutes, seconds
812 * and frames at 24 frames per second. Change the 'h' to the abbreviation
813 * for hours, 'm' to the abbreviation for minutes, 's' to the abbreviation
814 * for seconds and translate 'frames' . Don't change the numbers
815 * unless there aren't 60 seconds in a minute in your locale.
816 * The decimal separator is specified using '<' if your language uses a ',' or
817 * to '>' if your language uses a '.'. */
818 XO("0100 h 060 m 060 s+>24 frames")
819 },
820
821 {
822 /* i18n-hint: Name of time display format that shows time in frames (lots of
823 * frames) at 24 frames per second (commonly used for films) */
824 { XO("film frames (24 fps)") },
825 /* i18n-hint: Format string for displaying time in frames at 24 frames per
826 * second. Change the comma
827 * in the middle to the 1000s separator for your locale,
828 * translate 'frames' and leave the rest alone */
829 XO("01000,01000 frames|24")
830 },
831
832 {
833 /* i18n-hint: Name of time display format that shows time in hours, minutes,
834 * seconds and frames at NTSC TV drop-frame rate (used for American /
835 * Japanese TV, and very odd) */
836 { XO("hh:mm:ss + NTSC drop frames") },
837 /* i18n-hint: Format string for displaying time in hours, minutes, seconds
838 * and frames with NTSC drop frames. Change the 'h' to the abbreviation
839 * for hours, 'm' to the abbreviation for minutes, 's' to the abbreviation
840 * for seconds and translate 'frames'. Leave the |N alone, it's important!
841 * The decimal separator is specified using '<' if your language uses a ',' or
842 * to '>' if your language uses a '.'. */
843 XO("0100 h 060 m 060 s+>30 frames|N")
844 },
845
846 {
847 /* i18n-hint: Name of time display format that shows time in hours, minutes,
848 * seconds and frames at NTSC TV non-drop-frame rate (used for American /
849 * Japanese TV, and doesn't quite match wall time */
850 { XO("hh:mm:ss + NTSC non-drop frames") },
851 /* i18n-hint: Format string for displaying time in hours, minutes, seconds
852 * and frames with NTSC drop frames. Change the 'h' to the abbreviation
853 * for hours, 'm' to the abbreviation for minutes, 's' to the abbreviation
854 * for seconds and translate 'frames'. Leave the | .999000999 alone,
855 * the whole things really is slightly off-speed!
856 * The decimal separator is specified using '<' if your language uses a ',' or
857 * to '>' if your language uses a '.'. */
858 XO("0100 h 060 m 060 s+>030 frames| .999000999")
859 },
860
861 {
862 /* i18n-hint: Name of time display format that shows time in frames at NTSC
863 * TV frame rate (used for American / Japanese TV */
864 { XO("NTSC frames") },
865 /* i18n-hint: Format string for displaying time in frames with NTSC frames.
866 * Change the comma
867 * in the middle to the 1000s separator for your locale,
868 * translate 'frames' and leave the rest alone. That really is the frame
869 * rate! */
870 XO("01000,01000 frames|29.97002997")
871 },
872
873 {
874 /* i18n-hint: Name of time display format that shows time in hours, minutes,
875 * seconds and frames at PAL TV frame rate (used for European TV) */
876 { XO("hh:mm:ss + PAL frames (25 fps)") },
877 /* i18n-hint: Format string for displaying time in hours, minutes, seconds
878 * and frames with PAL TV frames. Change the 'h' to the abbreviation
879 * for hours, 'm' to the abbreviation for minutes, 's' to the abbreviation
880 * for seconds and translate 'frames'. Nice simple time code!
881 * The decimal separator is specified using '<' if your language uses a ',' or
882 * to '>' if your language uses a '.'. */
883 XO("0100 h 060 m 060 s+>25 frames")
884 },
885
886 {
887 /* i18n-hint: Name of time display format that shows time in frames at PAL
888 * TV frame rate (used for European TV) */
889 { XO("PAL frames (25 fps)") },
890 /* i18n-hint: Format string for displaying time in frames with NTSC frames.
891 * Change the comma
892 * in the middle to the 1000s separator for your locale,
893 * translate 'frames' and leave the rest alone. */
894 XO("01000,01000 frames|25")
895 },
896
897 {
898 /* i18n-hint: Name of time display format that shows time in hours, minutes,
899 * seconds and frames at CD Audio frame rate (75 frames per second) */
900 { XO("hh:mm:ss + CDDA frames (75 fps)") },
901 /* i18n-hint: Format string for displaying time in hours, minutes, seconds
902 * and frames with CD Audio frames. Change the 'h' to the abbreviation
903 * for hours, 'm' to the abbreviation for minutes, 's' to the abbreviation
904 * for seconds and translate 'frames'.
905 * The decimal separator is specified using '<' if your language uses a ',' or
906 * to '>' if your language uses a '.'. */
907 XO("0100 h 060 m 060 s+>75 frames")
908 },
909
910 {
911 /* i18n-hint: Name of time display format that shows time in frames at CD
912 * Audio frame rate (75 frames per second) */
913 { XO("CDDA frames (75 fps)") },
914 /* i18n-hint: Format string for displaying time in frames with CD Audio
915 * frames. Change the comma
916 * in the middle to the 1000s separator for your locale,
917 * translate 'frames' and leave the rest alone */
918 XO("01000,01000 frames|75")
919 },
920
921};
922
925};
926
932 {
934 {
935 /* i18n-hint: Format string for displaying frequency in hertz. Change
936 * the decimal point for your locale. Don't change the numbers.
937 * The decimal separator is specified using '<' if your language uses a ',' or
938 * to '>' if your language uses a '.'. */
939 XO("010,01000>0100 Hz")
940 , XO("centihertz")
941 }
942 },
943
944 {
945 /* i18n-hint: Name of display format that shows frequency in kilohertz */
946 { XO("kHz") },
947 {
948 /* i18n-hint: Format string for displaying frequency in kilohertz. Change
949 * the decimal point for your locale. Don't change the numbers.
950 * The decimal separator is specified using '<' if your language uses a ',' or
951 * to '>' if your language uses a '.'. */
952 XO("01000>01000 kHz|0.001")
953 , XO("hertz")
954 }
955 },
956};
957
960};
961
967 {
969 {
970 /* i18n-hint: Format string for displaying log of frequency in octaves.
971 * Change the decimal points for your locale. Don't change the numbers.
972 * The decimal separator is specified using '<' if your language uses a ',' or
973 * to '>' if your language uses a '.'. */
974 XO("100>01000 octaves|1.442695041"), // Scale factor is 1 / ln (2)
975 /* i18n-hint: an octave is a doubling of frequency */
976 XO("thousandths of octaves")
977 }
978 },
979
980 {
981 /* i18n-hint: Name of display format that shows log of frequency
982 * in semitones and cents */
983 { XO("semitones + cents") },
984 {
985 /* i18n-hint: Format string for displaying log of frequency in semitones
986 * and cents.
987 * Change the decimal points for your locale. Don't change the numbers.
988 * The decimal separator is specified using '<' if your language uses a ',' or
989 * to '>' if your language uses a '.'. */
990 XO("1000 semitones >0100 cents|17.312340491"), // Scale factor is 12 / ln (2)
991 /* i18n-hint: a cent is a hundredth of a semitone (which is 1/12 octave) */
992 XO("hundredths of cents")
993 }
994 },
995
996 {
997 /* i18n-hint: Name of display format that shows log of frequency
998 * in decades */
999 { XO("decades") },
1000 {
1001 /* i18n-hint: Format string for displaying log of frequency in decades.
1002 * Change the decimal points for your locale. Don't change the numbers. */
1003 XO("10>01000 decades|0.434294482"), // Scale factor is 1 / ln (10)
1004 /* i18n-hint: a decade is a tenfold increase of frequency */
1005 XO("thousandths of decades")
1006 }
1007 },
1008};
1009
1012};
1013
1016{
1017public:
1020 : mType { std::move(type) }
1021 , mFormat { std::move(format) }
1022 {
1023 // # in the format means that Sample Rate is used
1024 // to calculate the values. Otherwise, Sample Rate is optional for
1025 // the TIME and DURATION formats and ignored by the others.
1026 mDependsOnSampleRate = mFormat.Debug().find(L'#') != wxString::npos;
1027 }
1028
1029 std::unique_ptr<NumericConverterFormatter>
1030 Create(const FormatterContext& context) const override
1031 {
1032 if (!IsAcceptableInContext(context))
1033 return {};
1034
1035 return std::make_unique<ParsedNumericConverterFormatter>(
1036 mType, mFormat, context);
1037 }
1038
1039 bool IsAcceptableInContext(const FormatterContext& context) const override
1040 {
1041 return !mDependsOnSampleRate || context.HasSampleRate();
1042 }
1043
1044private:
1047
1049};
1050
1051static auto MakeItem(const NumericConverterType &type) {
1052 return [&type](const BuiltinFormatString &formatString) {
1053 const auto functionIdentifier = formatString.name.Internal();
1055 functionIdentifier, formatString.name,
1056 formatString.formatStrings.fraction,
1057 std::make_unique<ParsedNumericConverterFormatterFactory>(
1058 type, formatString.formatStrings.formatStr));
1059 };
1060}
1061
1062template<size_t N>
1063static auto MakeGroup (const Identifier& identifier, NumericConverterType type,
1064 const BuiltinFormatString (&formatStrings)[N])
1065{
1066 return NumericConverterFormatterGroup(identifier, { type },
1067 std::begin(formatStrings), std::end(formatStrings),
1068 MakeItem(type));
1069}
1070
1072 NumericConverterItems("parsed",
1073 // The sequence of these groups is unimportant because their numeric
1074 // converter types are all different
1075 MakeGroup("parsedTime",
1077 MakeGroup("parsedDuration", NumericConverterType_DURATION(),
1079 MakeGroup( "parsedFrequency", NumericConverterType_FREQUENCY(),
1081 MakeGroup("parsedBandwith", NumericConverterType_BANDWIDTH(),
1083 )
1084};
1085} // namespace
1086
1087std::unique_ptr<NumericConverterFormatter>
1089 const FormatterContext& context, NumericConverterType type,
1091{
1092 return std::make_unique<ParsedNumericConverterFormatter>(type, format, context);
1093}
wxT("CloseDown"))
XO("Cut/Copy/Paste")
#define field(n, t)
Definition: ImportAUP.cpp:165
constexpr auto NumericConverterItems
constexpr auto NumericConverterFormatterGroup
constexpr auto NumericConverterFormatterItem
const NumericConverterType & NumericConverterType_BANDWIDTH()
const NumericConverterType & NumericConverterType_DURATION()
const NumericConverterType & NumericConverterType_FREQUENCY()
const NumericConverterType & NumericConverterType_TIME()
std::unique_ptr< NumericConverterFormatter > CreateParsedNumericConverterFormatter(const FormatterContext &context, NumericConverterType type, const TranslatableString &format)
an object holding per-project preferred sample rate
const auto project
ComponentInterfaceSymbol pairs a persistent string identifier used internally with an optional,...
A context in which formatter operates.
bool HasSampleRate() const
Returns true if it is possible to get a sample rate from this context.
An explicitly nonlocalized string, not meant for the user to see.
Definition: Identifier.h:22
A move-only handle representing a connection to a Publisher.
Definition: Observer.h:70
A listener notified of changes in preferences.
Definition: Prefs.h:561
Holds project sample rate.
Definition: ProjectRate.h:24
static ProjectRate & Get(AudacityProject &project)
Definition: ProjectRate.cpp:28
Generates classes whose instances register items at construction.
Definition: Registry.h:388
Holds a msgid for the translation catalog; may also bind format arguments.
std::unique_ptr< NumericConverterFormatter > Create(const FormatterContext &context) const override
ParsedNumericConverterFormatter(NumericConverterType type, const TranslatableString &untranslatedFormat, const FormatterContext &context)
Positions or offsets within audio files need a wide type.
Definition: SampleCount.h:19
long long as_long_long() const
Definition: SampleCount.h:48
double as_double() const
Definition: SampleCount.h:46
NUMERIC_FORMATS_API NumericFormatSymbol HoursMinsSecondsFormat()
NUMERIC_FORMATS_API NumericFormatSymbol MillisecondsFormat()
NUMERIC_FORMATS_API NumericFormatSymbol HundredthsFormat()
NUMERIC_FORMATS_API NumericFormatSymbol HertzFormat()
NUMERIC_FORMATS_API NumericFormatSymbol SecondsFormat()
NUMERIC_FORMATS_API NumericFormatSymbol OctavesFormat()
NUMERIC_FORMATS_API NumericFormatSymbol TimeAndSampleFormat()
auto end(const Ptr< Type, BaseDeleter > &p)
Enables range-for.
Definition: PackedArray.h:159
auto begin(const Ptr< Type, BaseDeleter > &p)
Enables range-for.
Definition: PackedArray.h:150
constexpr size_t npos(-1)
static const BuiltinFormatString FrequencyConverterFormats_[]
array of formats the control knows about internally array of string pairs for name of the format and ...
NumericConverterFormats::DefaultFormatRegistrator frequencyDefault
NumericConverterFormats::DefaultFormatRegistrator bandwidthDefault
NumericConverterFormats::DefaultFormatRegistrator timeDefault
static auto MakeGroup(const Identifier &identifier, NumericConverterType type, const BuiltinFormatString(&formatStrings)[N])
static BuiltinFormatString TimeConverterFormats_[]
array of formats the control knows about internally array of string pairs for name of the format and ...
static const BuiltinFormatString BandwidthConverterFormats_[]
array of formats the control knows about internally array of string pairs for name of the format and ...
fastfloat_really_inline void round(adjusted_mantissa &am, callback cb) noexcept
Definition: fast_float.h:2512
STL namespace.
static NumericField ForRange(size_t range, bool zeropad=true, size_t minDigits=0)
struct to hold a formatting control string and its user facing name Used in an array to hold the buil...
friend bool operator==(const BuiltinFormatString &a, const BuiltinFormatString &b)
friend bool operator==(const FormatStrings &x, const FormatStrings &y)
friend bool operator!=(const FormatStrings &x, const FormatStrings &y)
FormatStrings(const TranslatableString &format={}, const TranslatableString &fraction={})