eeef52d7f8ca70d7eaf8975a7e8dda6fb5a1175b
[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 <string>
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 illegal_chars = "/\\*";
96         ustring legal;
97
98         legal = str;
99         pos = 0;
100
101         while ((pos = legal.find_first_of (illegal_chars, pos)) != string::npos) {
102                 legal.replace (pos, 1, "_");
103                 pos += 1;
104         }
105
106         return legal;
107 }
108 #if 0
109 string 
110 legalize_for_path (string str)
111 {
112         string::size_type pos;
113         string legal_chars = "abcdefghijklmnopqrtsuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_+=: ";
114         string legal;
115
116         legal = str;
117         pos = 0;
118
119         while ((pos = legal.find_first_not_of (legal_chars, pos)) != string::npos) {
120                 legal.replace (pos, 1, "_");
121                 pos += 1;
122         }
123
124         return legal;
125 }
126 #endif
127
128 string bump_name_once(std::string name)
129 {
130         string::size_type period;
131         string newname;
132
133         if ((period = name.find_last_of ('.')) == string::npos) {
134                 newname  = name;
135                 newname += ".1";
136         } else {
137                 int isnumber = 1;
138                 const char *last_element = name.c_str() + period + 1;
139                 for (size_t i = 0; i < strlen(last_element); i++) {
140                         if (!isdigit(last_element[i])) {
141                                 isnumber = 0;
142                                 break;
143                         }
144                 }
145
146                 errno = 0;
147                 long int version = strtol (name.c_str()+period+1, (char **)NULL, 10);
148
149                 if (isnumber == 0 || errno != 0) {
150                         // last_element is not a number, or is too large
151                         newname  = name;
152                         newname += ".1";
153                 } else {
154                         char buf[32];
155
156                         snprintf (buf, sizeof(buf), "%ld", version+1);
157                 
158                         newname  = name.substr (0, period+1);
159                         newname += buf;
160                 }
161         }
162
163         return newname;
164
165 }
166
167 ostream&
168 operator<< (ostream& o, const BBT_Time& bbt)
169 {
170         o << bbt.bars << '|' << bbt.beats << '|' << bbt.ticks;
171         return o;
172 }
173
174 XMLNode *
175 find_named_node (const XMLNode& node, string name)
176 {
177         XMLNodeList nlist;
178         XMLNodeConstIterator niter;
179         XMLNode* child;
180
181         nlist = node.children();
182
183         for (niter = nlist.begin(); niter != nlist.end(); ++niter) {
184
185                 child = *niter;
186
187                 if (child->name() == name) {
188                         return child;
189                 }
190         }
191
192         return 0;
193 }
194
195 int
196 cmp_nocase (const string& s, const string& s2)
197 {
198         string::const_iterator p = s.begin();
199         string::const_iterator p2 = s2.begin();
200         
201         while (p != s.end() && p2 != s2.end()) {
202                 if (toupper(*p) != toupper(*p2)) {
203                         return (toupper(*p) < toupper(*p2)) ? -1 : 1;
204                 }
205                 ++p;
206                 ++p2;
207         }
208         
209         return (s2.size() == s.size()) ? 0 : (s.size() < s2.size()) ? -1 : 1;
210 }
211
212 int
213 tokenize_fullpath (string fullpath, string& path, string& name)
214 {
215         string::size_type m = fullpath.find_last_of("/");
216         
217         if (m == string::npos) {
218                 path = fullpath;
219                 name = fullpath;
220                 return 1;
221         }
222
223         // does it look like just a directory?
224         if (m == fullpath.length()-1) {
225                 return -1;
226         }
227         path = fullpath.substr(0, m+1);
228         
229         string::size_type n = fullpath.find(".ardour", m);
230         // no .ardour?
231         if (n == string::npos) {
232                 return -1;
233         }
234         name = fullpath.substr(m+1, n - m - 1);
235         return 1;
236 }
237
238 int
239 touch_file (ustring path)
240 {
241         int fd = open (path.c_str(), O_RDWR|O_CREAT, 0660);
242         if (fd >= 0) {
243                 close (fd);
244                 return 0;
245         }
246         return 1;
247 }
248
249 ustring
250 region_name_from_path (ustring path, bool strip_channels, bool add_channel_suffix, uint32_t total, uint32_t this_one)
251 {
252         path = PBD::basename_nosuffix (path);
253
254         if (strip_channels) {
255
256                 /* remove any "?R", "?L" or "?[a-z]" channel identifier */
257                 
258                 ustring::size_type len = path.length();
259                 
260                 if (len > 3 && (path[len-2] == '%' || path[len-2] == '?' || path[len-2] == '.') && 
261                     (path[len-1] == 'R' || path[len-1] == 'L' || (islower (path[len-1])))) {
262                         
263                         path = path.substr (0, path.length() - 2);
264                 }
265         }
266
267         if (add_channel_suffix) {
268
269                 path += '%';
270                 
271                 if (total > 2) {
272                         path += (char) ('a' + this_one);
273                 } else {
274                         path += (char) (this_one == 0 ? 'L' : 'R');
275                 }
276         }
277
278         return path;
279 }       
280
281 bool
282 path_is_paired (ustring path, ustring& pair_base)
283 {
284         ustring::size_type pos;
285
286         /* remove filename suffixes etc. */
287         
288         if ((pos = path.find_last_of ('.')) != string::npos) {
289                 path = path.substr (0, pos);
290         }
291
292         ustring::size_type len = path.length();
293
294         /* look for possible channel identifier: "?R", "%R", ".L" etc. */
295
296         if (len > 3 && (path[len-2] == '%' || path[len-2] == '?' || path[len-2] == '.') && 
297             (path[len-1] == 'R' || path[len-1] == 'L' || (islower (path[len-1])))) {
298                 
299                 pair_base = path.substr (0, len-2);
300                 return true;
301
302         } 
303
304         return false;
305 }
306
307 ustring
308 path_expand (ustring path)
309 {
310 #ifdef HAVE_WORDEXP
311         /* Handle tilde and environment variable expansion in session path */
312         string ret = path;
313
314         wordexp_t expansion;
315         switch (wordexp (path.c_str(), &expansion, WRDE_NOCMD|WRDE_UNDEF)) {
316         case 0:
317                 break;
318         default:
319                 error << string_compose (_("illegal or badly-formed string used for path (%1)"), path) << endmsg;
320                 goto out;
321         }
322
323         if (expansion.we_wordc > 1) {
324                 error << string_compose (_("path (%1) is ambiguous"), path) << endmsg;
325                 goto out;
326         }
327
328         ret = expansion.we_wordv[0];
329   out:
330         wordfree (&expansion);
331         return ret;
332
333 #else 
334         return path;
335 #endif
336 }
337
338 #if defined(HAVE_COREAUDIO) || defined(HAVE_AUDIOUNITS)
339 string 
340 CFStringRefToStdString(CFStringRef stringRef)
341 {
342         CFIndex size = 
343                 CFStringGetMaximumSizeForEncoding(CFStringGetLength(stringRef) , 
344                 kCFStringEncodingUTF8);
345             char *buf = new char[size];
346         
347         std::string result;
348
349         if(CFStringGetCString(stringRef, buf, size, kCFStringEncodingUTF8)) {
350             result = buf;
351         }
352         delete [] buf;
353         return result;
354 }
355 #endif // HAVE_COREAUDIO
356
357 void
358 compute_equal_power_fades (nframes_t nframes, float* in, float* out)
359 {
360         double step;
361
362         step = 1.0/nframes;
363
364         in[0] = 0.0f;
365         
366         for (nframes_t i = 1; i < nframes - 1; ++i) {
367                 in[i] = in[i-1] + step;
368         }
369         
370         in[nframes-1] = 1.0;
371
372         const float pan_law_attenuation = -3.0f;
373         const float scale = 2.0f - 4.0f * powf (10.0f,pan_law_attenuation/20.0f);
374
375         for (nframes_t n = 0; n < nframes; ++n) {
376                 float inVal = in[n];
377                 float outVal = 1 - inVal;
378                 out[n] = outVal * (scale * outVal + 1.0f - scale);
379                 in[n] = inVal * (scale * inVal + 1.0f - scale);
380         }
381 }
382
383 EditMode
384 string_to_edit_mode (string str)
385 {
386         if (str == _("Splice Edit")) {
387                 return Splice;
388         } else if (str == _("Slide Edit")) {
389                 return Slide;
390         } else if (str == _("Lock Edit")) {
391                 return Lock;
392         }
393         fatal << string_compose (_("programming error: unknown edit mode string \"%1\""), str) << endmsg;
394         /*NOTREACHED*/
395         return Slide;
396 }
397
398 const char*
399 edit_mode_to_string (EditMode mode)
400 {
401         switch (mode) {
402         case Slide:
403                 return _("Slide Edit");
404
405         case Lock:
406                 return _("Lock Edit");
407
408         default:
409         case Splice:
410                 return _("Splice Edit");
411         }
412 }
413
414 SlaveSource
415 string_to_slave_source (string str)
416 {
417         if (str == _("Internal")) {
418                 return None;
419         }
420         
421         if (str == _("MTC")) {
422                 return MTC;
423         }
424
425         if (str == _("JACK")) {
426                 return JACK;
427         }
428
429         fatal << string_compose (_("programming error: unknown slave source string \"%1\""), str) << endmsg;
430         /*NOTREACHED*/
431         return None;
432 }
433
434 const char*
435 slave_source_to_string (SlaveSource src)
436 {
437         switch (src) {
438         case JACK:
439                 return _("JACK");
440
441         case MTC:
442                 return _("MTC");
443                 
444         default:
445         case None:
446                 return _("Internal");
447                 
448         }
449 }
450
451 /* I don't really like hard-coding these falloff rates here
452  * Probably should use a map of some kind that could be configured
453  * These rates are db/sec.
454 */
455
456 #define METER_FALLOFF_OFF     0.0f
457 #define METER_FALLOFF_SLOWEST 6.6f // BBC standard
458 #define METER_FALLOFF_SLOW    8.6f // BBC standard
459 #define METER_FALLOFF_MEDIUM  20.0f
460 #define METER_FALLOFF_FAST    32.0f
461 #define METER_FALLOFF_FASTER  46.0f
462 #define METER_FALLOFF_FASTEST 70.0f
463
464 float
465 meter_falloff_to_float (MeterFalloff falloff)
466 {
467         switch (falloff) {
468         case MeterFalloffOff:
469                 return METER_FALLOFF_OFF;
470         case MeterFalloffSlowest:
471                 return METER_FALLOFF_SLOWEST;
472         case MeterFalloffSlow:
473                 return METER_FALLOFF_SLOW;
474         case MeterFalloffMedium:
475                 return METER_FALLOFF_MEDIUM;
476         case MeterFalloffFast:
477                 return METER_FALLOFF_FAST;
478         case MeterFalloffFaster:
479                 return METER_FALLOFF_FASTER;
480         case MeterFalloffFastest:
481                 return METER_FALLOFF_FASTEST;
482         default:
483                 return METER_FALLOFF_FAST;
484         }
485 }
486
487 MeterFalloff
488 meter_falloff_from_float (float val)
489 {
490         if (val == METER_FALLOFF_OFF) {
491                 return MeterFalloffOff;
492         }
493         else if (val <= METER_FALLOFF_SLOWEST) {
494                 return MeterFalloffSlowest;
495         }
496         else if (val <= METER_FALLOFF_SLOW) {
497                 return MeterFalloffSlow;
498         }
499         else if (val <= METER_FALLOFF_MEDIUM) {
500                 return MeterFalloffMedium;
501         }
502         else if (val <= METER_FALLOFF_FAST) {
503                 return MeterFalloffFast;
504         }
505         else if (val <= METER_FALLOFF_FASTER) {
506                 return MeterFalloffFaster;
507         }
508         else {
509                 return MeterFalloffFastest;
510         }
511 }
512
513 float
514 meter_hold_to_float (MeterHold hold)
515 {
516         switch (hold) {
517         case MeterHoldOff:
518                 return 0.0f;
519         case MeterHoldShort:
520                 return 40.0f;
521         case MeterHoldMedium:
522                 return 100.0f;
523         case MeterHoldLong:
524         default:
525                 return 200.0f;
526         }
527 }
528
529 AutoState 
530 ARDOUR::string_to_auto_state (std::string str)
531 {
532         if (str == X_("Off")) {
533                 return Off;
534         } else if (str == X_("Play")) {
535                 return Play;
536         } else if (str == X_("Write")) {
537                 return Write;
538         } else if (str == X_("Touch")) {
539                 return Touch;
540         }
541
542         fatal << string_compose (_("programming error: %1 %2"), "illegal AutoState string: ", str) << endmsg;
543         /*NOTREACHED*/
544         return Touch;
545 }
546
547 string 
548 ARDOUR::auto_state_to_string (AutoState as)
549 {
550         /* to be used only for XML serialization, no i18n done */
551
552         switch (as) {
553         case Off:
554                 return X_("Off");
555                 break;
556         case Play:
557                 return X_("Play");
558                 break;
559         case Write:
560                 return X_("Write");
561                 break;
562         case Touch:
563                 return X_("Touch");
564         }
565
566         fatal << string_compose (_("programming error: %1 %2"), "illegal AutoState type: ", as) << endmsg;
567         /*NOTREACHED*/
568         return "";
569 }
570
571 AutoStyle 
572 ARDOUR::string_to_auto_style (std::string str)
573 {
574         if (str == X_("Absolute")) {
575                 return Absolute;
576         } else if (str == X_("Trim")) {
577                 return Trim;
578         }
579
580         fatal << string_compose (_("programming error: %1 %2"), "illegal AutoStyle string: ", str) << endmsg;
581         /*NOTREACHED*/
582         return Trim;
583 }
584
585 string 
586 ARDOUR::auto_style_to_string (AutoStyle as)
587 {
588         /* to be used only for XML serialization, no i18n done */
589
590         switch (as) {
591         case Absolute:
592                 return X_("Absolute");
593                 break;
594         case Trim:
595                 return X_("Trim");
596                 break;
597         }
598
599         fatal << string_compose (_("programming error: %1 %2"), "illegal AutoStyle type: ", as) << endmsg;
600         /*NOTREACHED*/
601         return "";
602 }
603
604 extern "C" {
605         void c_stacktrace() { stacktrace (cerr); }
606 }