slightly improved string_is_affirmative() implementation
[ardour.git] / libs / ardour / utils.cc
1 /*
2     Copyright (C) 2000-2003 Paul Davis 
3
4     This program is free software; you can redistribute it and/or modify
5     it under the terms of the GNU General Public License as published by
6     the Free Software Foundation; either version 2 of the License, or
7     (at your option) any later version.
8
9     This program is distributed in the hope that it will be useful,
10     but WITHOUT ANY WARRANTY; without even the implied warranty of
11     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12     GNU General Public License for more details.
13
14     You should have received a copy of the GNU General Public License
15     along with this program; if not, write to the Free Software
16     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17
18 */
19
20 #include <cstdio> /* for sprintf */
21 #include <cmath>
22 #include <cctype>
23 #include <cstring>
24 #include <cerrno>
25 #include <iostream>
26 #include <sys/types.h>
27 #include <sys/stat.h>
28 #include <sys/time.h>
29 #include <fcntl.h>
30 #include <unistd.h>
31
32 #ifdef HAVE_WORDEXP
33 #include <wordexp.h>
34 #endif
35
36 #include <pbd/error.h>
37 #include <pbd/stacktrace.h>
38 #include <pbd/xml++.h>
39 #include <pbd/basename.h>
40 #include <ardour/utils.h>
41
42 #include "i18n.h"
43
44 using namespace ARDOUR;
45 using namespace std;
46 using namespace PBD;
47 using Glib::ustring;
48
49 void
50 elapsed_time_to_str (char *buf, uint32_t seconds)
51
52 {
53         uint32_t days;
54         uint32_t hours;
55         uint32_t minutes;
56         uint32_t s;
57
58         s = seconds;
59         days = s / (3600 * 24);
60         s -= (days * 3600 * 24);
61         hours = s / 3600;
62         s -= (hours * 3600);
63         minutes = s / 60;
64         s -= minutes * 60;
65         
66         if (days) {
67                 snprintf (buf, sizeof (buf), "%" PRIu32 " day%s %" PRIu32 " hour%s", 
68                          days, 
69                          days > 1 ? "s" : "",
70                          hours,
71                          hours > 1 ? "s" : "");
72         } else if (hours) {
73                 snprintf (buf, sizeof (buf), "%" PRIu32 " hour%s %" PRIu32 " minute%s", 
74                          hours, 
75                          hours > 1 ? "s" : "",
76                          minutes,
77                          minutes > 1 ? "s" : "");
78         } else if (minutes) {
79                 snprintf (buf, sizeof (buf), "%" PRIu32 " minute%s", 
80                          minutes,
81                          minutes > 1 ? "s" : "");
82         } else if (s) {
83                 snprintf (buf, sizeof (buf), "%" PRIu32 " second%s", 
84                          seconds,
85                          seconds > 1 ? "s" : "");
86         } else {
87                 snprintf (buf, sizeof (buf), "no time");
88         }
89 }
90
91 ustring 
92 legalize_for_path (ustring str)
93 {
94         ustring::size_type pos;
95         ustring legal_chars = "abcdefghijklmnopqrtsuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_+=: ";
96         ustring legal;
97         
98         legal = str;
99         pos = 0;
100
101         while ((pos = legal.find_first_not_of (legal_chars, pos)) != string::npos) {
102                 legal.replace (pos, 1, "_");
103                 pos += 1;
104         }
105
106         return legal;
107 }
108
109 string bump_name_once(std::string name)
110 {
111         string::size_type period;
112         string newname;
113
114         if ((period = name.find_last_of ('.')) == string::npos) {
115                 newname  = name;
116                 newname += ".1";
117         } else {
118                 int isnumber = 1;
119                 const char *last_element = name.c_str() + period + 1;
120                 for (size_t i = 0; i < strlen(last_element); i++) {
121                         if (!isdigit(last_element[i])) {
122                                 isnumber = 0;
123                                 break;
124                         }
125                 }
126
127                 errno = 0;
128                 long int version = strtol (name.c_str()+period+1, (char **)NULL, 10);
129
130                 if (isnumber == 0 || errno != 0) {
131                         // last_element is not a number, or is too large
132                         newname  = name;
133                         newname += ".1";
134                 } else {
135                         char buf[32];
136
137                         snprintf (buf, sizeof(buf), "%ld", version+1);
138                 
139                         newname  = name.substr (0, period+1);
140                         newname += buf;
141                 }
142         }
143
144         return newname;
145
146 }
147
148 ostream&
149 operator<< (ostream& o, const BBT_Time& bbt)
150 {
151         o << bbt.bars << '|' << bbt.beats << '|' << bbt.ticks;
152         return o;
153 }
154
155 XMLNode *
156 find_named_node (const XMLNode& node, string name)
157 {
158         XMLNodeList nlist;
159         XMLNodeConstIterator niter;
160         XMLNode* child;
161
162         nlist = node.children();
163
164         for (niter = nlist.begin(); niter != nlist.end(); ++niter) {
165
166                 child = *niter;
167
168                 if (child->name() == name) {
169                         return child;
170                 }
171         }
172
173         return 0;
174 }
175
176 int
177 cmp_nocase (const string& s, const string& s2)
178 {
179         string::const_iterator p = s.begin();
180         string::const_iterator p2 = s2.begin();
181         
182         while (p != s.end() && p2 != s2.end()) {
183                 if (toupper(*p) != toupper(*p2)) {
184                         return (toupper(*p) < toupper(*p2)) ? -1 : 1;
185                 }
186                 ++p;
187                 ++p2;
188         }
189         
190         return (s2.size() == s.size()) ? 0 : (s.size() < s2.size()) ? -1 : 1;
191 }
192
193 int
194 tokenize_fullpath (string fullpath, string& path, string& name)
195 {
196         string::size_type m = fullpath.find_last_of("/");
197         
198         if (m == string::npos) {
199                 path = fullpath;
200                 name = fullpath;
201                 return 1;
202         }
203
204         // does it look like just a directory?
205         if (m == fullpath.length()-1) {
206                 return -1;
207         }
208         path = fullpath.substr(0, m+1);
209         
210         string::size_type n = fullpath.find(".ardour", m);
211         // no .ardour?
212         if (n == string::npos) {
213                 return -1;
214         }
215         name = fullpath.substr(m+1, n - m - 1);
216         return 1;
217 }
218
219 int
220 touch_file (ustring path)
221 {
222         int fd = open (path.c_str(), O_RDWR|O_CREAT, 0660);
223         if (fd >= 0) {
224                 close (fd);
225                 return 0;
226         }
227         return 1;
228 }
229
230 ustring
231 region_name_from_path (ustring path, bool strip_channels, bool add_channel_suffix, uint32_t total, uint32_t this_one)
232 {
233         path = PBD::basename_nosuffix (path);
234
235         if (strip_channels) {
236
237                 /* remove any "?R", "?L" or "?[a-z]" channel identifier */
238                 
239                 ustring::size_type len = path.length();
240                 
241                 if (len > 3 && (path[len-2] == '%' || path[len-2] == '?' || path[len-2] == '.') && 
242                     (path[len-1] == 'R' || path[len-1] == 'L' || (islower (path[len-1])))) {
243                         
244                         path = path.substr (0, path.length() - 2);
245                 }
246         }
247
248         if (add_channel_suffix) {
249
250                 path += '%';
251                 
252                 if (total > 2) {
253                         path += (char) ('a' + this_one);
254                 } else {
255                         path += (char) (this_one == 0 ? 'L' : 'R');
256                 }
257         }
258
259         return path;
260 }       
261
262 bool
263 path_is_paired (ustring path, ustring& pair_base)
264 {
265         ustring::size_type pos;
266
267         /* remove filename suffixes etc. */
268         
269         if ((pos = path.find_last_of ('.')) != string::npos) {
270                 path = path.substr (0, pos);
271         }
272
273         ustring::size_type len = path.length();
274
275         /* look for possible channel identifier: "?R", "%R", ".L" etc. */
276
277         if (len > 3 && (path[len-2] == '%' || path[len-2] == '?' || path[len-2] == '.') && 
278             (path[len-1] == 'R' || path[len-1] == 'L' || (islower (path[len-1])))) {
279                 
280                 pair_base = path.substr (0, len-2);
281                 return true;
282
283         } 
284
285         return false;
286 }
287
288 ustring
289 path_expand (ustring path)
290 {
291 #ifdef HAVE_WORDEXP
292         /* Handle tilde and environment variable expansion in session path */
293         string ret = path;
294
295         wordexp_t expansion;
296         switch (wordexp (path.c_str(), &expansion, WRDE_NOCMD|WRDE_UNDEF)) {
297         case 0:
298                 break;
299         default:
300                 error << string_compose (_("illegal or badly-formed string used for path (%1)"), path) << endmsg;
301                 goto out;
302         }
303
304         if (expansion.we_wordc > 1) {
305                 error << string_compose (_("path (%1) is ambiguous"), path) << endmsg;
306                 goto out;
307         }
308
309         ret = expansion.we_wordv[0];
310   out:
311         wordfree (&expansion);
312         return ret;
313
314 #else 
315         return path;
316 #endif
317 }
318
319 #if defined(HAVE_COREAUDIO) || defined(HAVE_AUDIOUNITS)
320 string 
321 CFStringRefToStdString(CFStringRef stringRef)
322 {
323         CFIndex size = 
324                 CFStringGetMaximumSizeForEncoding(CFStringGetLength(stringRef) , 
325                 kCFStringEncodingUTF8);
326             char *buf = new char[size];
327         
328         std::string result;
329
330         if(CFStringGetCString(stringRef, buf, size, kCFStringEncodingUTF8)) {
331             result = buf;
332         }
333         delete [] buf;
334         return result;
335 }
336 #endif // HAVE_COREAUDIO
337
338 void
339 compute_equal_power_fades (nframes_t nframes, float* in, float* out)
340 {
341         double step;
342
343         step = 1.0/(nframes-1);
344
345         in[0] = 0.0f;
346         
347         for (nframes_t i = 1; i < nframes - 1; ++i) {
348                 in[i] = in[i-1] + step;
349         }
350         
351         in[nframes-1] = 1.0;
352
353         const float pan_law_attenuation = -3.0f;
354         const float scale = 2.0f - 4.0f * powf (10.0f,pan_law_attenuation/20.0f);
355
356         for (nframes_t n = 0; n < nframes; ++n) {
357                 float inVal = in[n];
358                 float outVal = 1 - inVal;
359                 out[n] = outVal * (scale * outVal + 1.0f - scale);
360                 in[n] = inVal * (scale * inVal + 1.0f - scale);
361         }
362 }
363
364 EditMode
365 string_to_edit_mode (string str)
366 {
367         if (str == _("Splice Edit")) {
368                 return Splice;
369         } else if (str == _("Slide Edit")) {
370                 return Slide;
371         } else if (str == _("Lock Edit")) {
372                 return Lock;
373         }
374         fatal << string_compose (_("programming error: unknown edit mode string \"%1\""), str) << endmsg;
375         /*NOTREACHED*/
376         return Slide;
377 }
378
379 const char*
380 edit_mode_to_string (EditMode mode)
381 {
382         switch (mode) {
383         case Slide:
384                 return _("Slide Edit");
385
386         case Lock:
387                 return _("Lock Edit");
388
389         default:
390         case Splice:
391                 return _("Splice Edit");
392         }
393 }
394
395 SlaveSource
396 string_to_slave_source (string str)
397 {
398         if (str == _("Internal")) {
399                 return None;
400         }
401         
402         if (str == _("MTC")) {
403                 return MTC;
404         }
405
406         if (str == _("JACK")) {
407                 return JACK;
408         }
409
410         fatal << string_compose (_("programming error: unknown slave source string \"%1\""), str) << endmsg;
411         /*NOTREACHED*/
412         return None;
413 }
414
415 const char*
416 slave_source_to_string (SlaveSource src)
417 {
418         switch (src) {
419         case JACK:
420                 return _("JACK");
421
422         case MTC:
423                 return _("MTC");
424                 
425         default:
426         case None:
427                 return _("Internal");
428                 
429         }
430 }
431
432 /* I don't really like hard-coding these falloff rates here
433  * Probably should use a map of some kind that could be configured
434  * These rates are db/sec.
435 */
436
437 #define METER_FALLOFF_OFF     0.0f
438 #define METER_FALLOFF_SLOWEST 6.6f // BBC standard
439 #define METER_FALLOFF_SLOW    8.6f // BBC standard
440 #define METER_FALLOFF_MEDIUM  20.0f
441 #define METER_FALLOFF_FAST    32.0f
442 #define METER_FALLOFF_FASTER  46.0f
443 #define METER_FALLOFF_FASTEST 70.0f
444
445 float
446 meter_falloff_to_float (MeterFalloff falloff)
447 {
448         switch (falloff) {
449         case MeterFalloffOff:
450                 return METER_FALLOFF_OFF;
451         case MeterFalloffSlowest:
452                 return METER_FALLOFF_SLOWEST;
453         case MeterFalloffSlow:
454                 return METER_FALLOFF_SLOW;
455         case MeterFalloffMedium:
456                 return METER_FALLOFF_MEDIUM;
457         case MeterFalloffFast:
458                 return METER_FALLOFF_FAST;
459         case MeterFalloffFaster:
460                 return METER_FALLOFF_FASTER;
461         case MeterFalloffFastest:
462                 return METER_FALLOFF_FASTEST;
463         default:
464                 return METER_FALLOFF_FAST;
465         }
466 }
467
468 MeterFalloff
469 meter_falloff_from_float (float val)
470 {
471         if (val == METER_FALLOFF_OFF) {
472                 return MeterFalloffOff;
473         }
474         else if (val <= METER_FALLOFF_SLOWEST) {
475                 return MeterFalloffSlowest;
476         }
477         else if (val <= METER_FALLOFF_SLOW) {
478                 return MeterFalloffSlow;
479         }
480         else if (val <= METER_FALLOFF_MEDIUM) {
481                 return MeterFalloffMedium;
482         }
483         else if (val <= METER_FALLOFF_FAST) {
484                 return MeterFalloffFast;
485         }
486         else if (val <= METER_FALLOFF_FASTER) {
487                 return MeterFalloffFaster;
488         }
489         else {
490                 return MeterFalloffFastest;
491         }
492 }
493
494 float
495 meter_hold_to_float (MeterHold hold)
496 {
497         switch (hold) {
498         case MeterHoldOff:
499                 return 0.0f;
500         case MeterHoldShort:
501                 return 40.0f;
502         case MeterHoldMedium:
503                 return 100.0f;
504         case MeterHoldLong:
505         default:
506                 return 200.0f;
507         }
508 }
509
510 AutoState 
511 ARDOUR::string_to_auto_state (std::string str)
512 {
513         if (str == X_("Off")) {
514                 return Off;
515         } else if (str == X_("Play")) {
516                 return Play;
517         } else if (str == X_("Write")) {
518                 return Write;
519         } else if (str == X_("Touch")) {
520                 return Touch;
521         }
522
523         fatal << string_compose (_("programming error: %1 %2"), "illegal AutoState string: ", str) << endmsg;
524         /*NOTREACHED*/
525         return Touch;
526 }
527
528 string 
529 ARDOUR::auto_state_to_string (AutoState as)
530 {
531         /* to be used only for XML serialization, no i18n done */
532
533         switch (as) {
534         case Off:
535                 return X_("Off");
536                 break;
537         case Play:
538                 return X_("Play");
539                 break;
540         case Write:
541                 return X_("Write");
542                 break;
543         case Touch:
544                 return X_("Touch");
545         }
546
547         fatal << string_compose (_("programming error: %1 %2"), "illegal AutoState type: ", as) << endmsg;
548         /*NOTREACHED*/
549         return "";
550 }
551
552 AutoStyle 
553 ARDOUR::string_to_auto_style (std::string str)
554 {
555         if (str == X_("Absolute")) {
556                 return Absolute;
557         } else if (str == X_("Trim")) {
558                 return Trim;
559         }
560
561         fatal << string_compose (_("programming error: %1 %2"), "illegal AutoStyle string: ", str) << endmsg;
562         /*NOTREACHED*/
563         return Trim;
564 }
565
566 string 
567 ARDOUR::auto_style_to_string (AutoStyle as)
568 {
569         /* to be used only for XML serialization, no i18n done */
570
571         switch (as) {
572         case Absolute:
573                 return X_("Absolute");
574                 break;
575         case Trim:
576                 return X_("Trim");
577                 break;
578         }
579
580         fatal << string_compose (_("programming error: %1 %2"), "illegal AutoStyle type: ", as) << endmsg;
581         /*NOTREACHED*/
582         return "";
583 }
584 bool
585 string_is_affirmative (const std::string& str)
586 {
587         /* to be used only with XML data - not intended to handle user input */
588
589         if (str == "1" || str == "y" || str == "Y") {
590                 return true;
591         } else {
592                 std::string str_uc;
593                 std::transform(str.begin(), str.end(), str_uc.begin(), ::toupper);
594                 if (str_uc == "YES") {
595                         return true;
596                 }
597         }
598         return false;
599 }
600
601 extern "C" {
602         void c_stacktrace() { stacktrace (cerr); }
603 }