Assorted c++11 cleanups.
[libdcp.git] / src / types.cc
1 /*
2     Copyright (C) 2012-2019 Carl Hetherington <cth@carlh.net>
3
4     This file is part of libdcp.
5
6     libdcp is free software; you can redistribute it and/or modify
7     it under the terms of the GNU General Public License as published by
8     the Free Software Foundation; either version 2 of the License, or
9     (at your option) any later version.
10
11     libdcp is distributed in the hope that it will be useful,
12     but WITHOUT ANY WARRANTY; without even the implied warranty of
13     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14     GNU General Public License for more details.
15
16     You should have received a copy of the GNU General Public License
17     along with libdcp.  If not, see <http://www.gnu.org/licenses/>.
18
19     In addition, as a special exception, the copyright holders give
20     permission to link the code of portions of this program with the
21     OpenSSL library under certain conditions as described in each
22     individual source file, and distribute linked combinations
23     including the two.
24
25     You must obey the GNU General Public License in all respects
26     for all of the code used other than OpenSSL.  If you modify
27     file(s) with this exception, you may extend this exception to your
28     version of the file(s), but you are not obligated to do so.  If you
29     do not wish to do so, delete this exception statement from your
30     version.  If you delete this exception statement from all source
31     files in the program, then also delete it here.
32 */
33
34 #include "raw_convert.h"
35 #include "types.h"
36 #include "exceptions.h"
37 #include "compose.hpp"
38 #include "dcp_assert.h"
39 #include <libxml++/libxml++.h>
40 #include <boost/algorithm/string.hpp>
41 #include <string>
42 #include <vector>
43 #include <cmath>
44 #include <cstdio>
45 #include <iomanip>
46
47 using std::string;
48 using std::ostream;
49 using std::vector;
50 using namespace dcp;
51 using namespace boost;
52
53 bool dcp::operator== (dcp::Size const & a, dcp::Size const & b)
54 {
55         return (a.width == b.width && a.height == b.height);
56 }
57
58 bool dcp::operator!= (dcp::Size const & a, dcp::Size const & b)
59 {
60         return !(a == b);
61 }
62
63
64 /** Construct a Fraction from a string of the form <numerator> <denominator>
65  *  e.g. "1 3".
66  */
67 Fraction::Fraction (string s)
68 {
69         vector<string> b;
70         split (b, s, is_any_of (" "));
71         if (b.size() != 2) {
72                 boost::throw_exception (XMLError ("malformed fraction " + s + " in XML node"));
73         }
74         numerator = raw_convert<int> (b[0]);
75         denominator = raw_convert<int> (b[1]);
76 }
77
78 string
79 Fraction::as_string () const
80 {
81         return String::compose ("%1 %2", numerator, denominator);
82 }
83
84 bool
85 dcp::operator== (Fraction const & a, Fraction const & b)
86 {
87         return (a.numerator == b.numerator && a.denominator == b.denominator);
88 }
89
90 bool
91 dcp::operator!= (Fraction const & a, Fraction const & b)
92 {
93         return (a.numerator != b.numerator || a.denominator != b.denominator);
94 }
95
96
97 /** Construct a Colour, initialising it to black. */
98 Colour::Colour ()
99         : r (0)
100         , g (0)
101         , b (0)
102 {
103
104 }
105
106 /** Construct a Colour from R, G and B.  The values run between
107  *  0 and 255.
108  */
109 Colour::Colour (int r_, int g_, int b_)
110         : r (r_)
111         , g (g_)
112         , b (b_)
113 {
114
115 }
116
117 /** Construct a Colour from an ARGB hex string; the alpha value is ignored.
118  *  @param argb_hex A string of the form AARRGGBB, where e.g. RR is a two-character
119  *  hex value.
120  */
121 Colour::Colour (string argb_hex)
122 {
123         int alpha;
124         if (sscanf (argb_hex.c_str(), "%2x%2x%2x%2x", &alpha, &r, &g, &b) != 4) {
125                 boost::throw_exception (XMLError ("could not parse colour string"));
126         }
127 }
128
129 /** @return An ARGB string of the form AARRGGBB, where e.g. RR is a two-character
130  *  hex value.  The alpha value will always be FF (ie 255; maximum alpha).
131  */
132 string
133 Colour::to_argb_string () const
134 {
135         char buffer[9];
136         snprintf (buffer, sizeof(buffer), "FF%02X%02X%02X", r, g, b);
137         return buffer;
138 }
139
140 /** @return An RGB string of the form RRGGBB, where e.g. RR is a two-character
141  *  hex value.
142  */
143 string
144 Colour::to_rgb_string () const
145 {
146         char buffer[7];
147         snprintf (buffer, sizeof(buffer), "%02X%02X%02X", r, g, b);
148         return buffer;
149 }
150
151 /** operator== for Colours.
152  *  @param a First colour to compare.
153  *  @param b Second colour to compare.
154  */
155 bool
156 dcp::operator== (Colour const & a, Colour const & b)
157 {
158         return (a.r == b.r && a.g == b.g && a.b == b.b);
159 }
160
161 /** operator!= for Colours.
162  *  @param a First colour to compare.
163  *  @param b Second colour to compare.
164  */
165 bool
166 dcp::operator!= (Colour const & a, Colour const & b)
167 {
168         return !(a == b);
169 }
170
171
172 string
173 dcp::effect_to_string (Effect e)
174 {
175         switch (e) {
176         case Effect::NONE:
177                 return "none";
178         case Effect::BORDER:
179                 return "border";
180         case Effect::SHADOW:
181                 return "shadow";
182         }
183
184         boost::throw_exception (MiscError ("unknown effect type"));
185 }
186
187 Effect
188 dcp::string_to_effect (string s)
189 {
190         if (s == "none") {
191                 return Effect::NONE;
192         } else if (s == "border") {
193                 return Effect::BORDER;
194         } else if (s == "shadow") {
195                 return Effect::SHADOW;
196         }
197
198         boost::throw_exception (ReadError ("unknown subtitle effect type"));
199 }
200
201
202 string
203 dcp::halign_to_string (HAlign h)
204 {
205         switch (h) {
206         case HAlign::LEFT:
207                 return "left";
208         case HAlign::CENTER:
209                 return "center";
210         case HAlign::RIGHT:
211                 return "right";
212         }
213
214         boost::throw_exception (MiscError ("unknown subtitle halign type"));
215 }
216
217 HAlign
218 dcp::string_to_halign (string s)
219 {
220         if (s == "left") {
221                 return HAlign::LEFT;
222         } else if (s == "center") {
223                 return HAlign::CENTER;
224         } else if (s == "right") {
225                 return HAlign::RIGHT;
226         }
227
228         boost::throw_exception (ReadError ("unknown subtitle halign type"));
229 }
230
231 string
232 dcp::valign_to_string (VAlign v)
233 {
234         switch (v) {
235         case VAlign::TOP:
236                 return "top";
237         case VAlign::CENTER:
238                 return "center";
239         case VAlign::BOTTOM:
240                 return "bottom";
241         }
242
243         boost::throw_exception (MiscError ("unknown subtitle valign type"));
244 }
245
246 VAlign
247 dcp::string_to_valign (string s)
248 {
249         if (s == "top") {
250                 return VAlign::TOP;
251         } else if (s == "center") {
252                 return VAlign::CENTER;
253         } else if (s == "bottom") {
254                 return VAlign::BOTTOM;
255         }
256
257         boost::throw_exception (ReadError ("unknown subtitle valign type"));
258 }
259
260 string
261 dcp::direction_to_string (Direction v)
262 {
263         switch (v) {
264         case Direction::LTR:
265                 return "ltr";
266         case Direction::RTL:
267                 return "rtl";
268         case Direction::TTB:
269                 return "ttb";
270         case Direction::BTT:
271                 return "btt";
272         }
273
274         boost::throw_exception (MiscError ("unknown subtitle direction type"));
275 }
276
277 Direction
278 dcp::string_to_direction (string s)
279 {
280         if (s == "ltr" || s == "horizontal") {
281                 return Direction::LTR;
282         } else if (s == "rtl") {
283                 return Direction::RTL;
284         } else if (s == "ttb" || s == "vertical") {
285                 return Direction::TTB;
286         } else if (s == "btt") {
287                 return Direction::BTT;
288         }
289
290         boost::throw_exception (ReadError ("unknown subtitle direction type"));
291 }
292
293 /** Convert a content kind to a string which can be used in a
294  *  &lt;ContentKind&gt; node.
295  *  @param kind ContentKind.
296  *  @return string.
297  */
298 string
299 dcp::content_kind_to_string (ContentKind kind)
300 {
301         switch (kind) {
302         case ContentKind::FEATURE:
303                 return "feature";
304         case ContentKind::SHORT:
305                 return "short";
306         case ContentKind::TRAILER:
307                 return "trailer";
308         case ContentKind::TEST:
309                 return "test";
310         case ContentKind::TRANSITIONAL:
311                 return "transitional";
312         case ContentKind::RATING:
313                 return "rating";
314         case ContentKind::TEASER:
315                 return "teaser";
316         case ContentKind::POLICY:
317                 return "policy";
318         case ContentKind::PUBLIC_SERVICE_ANNOUNCEMENT:
319                 return "psa";
320         case ContentKind::ADVERTISEMENT:
321                 return "advertisement";
322         case ContentKind::EPISODE:
323                 return "episode";
324         case ContentKind::PROMO:
325                 return "promo";
326         }
327
328         DCP_ASSERT (false);
329 }
330
331 /** Convert a string from a &lt;ContentKind&gt; node to a libdcp ContentKind.
332  *  Reasonably tolerant about varying case.
333  *  @param kind Content kind string.
334  *  @return libdcp ContentKind.
335  */
336 dcp::ContentKind
337 dcp::content_kind_from_string (string kind)
338 {
339         transform (kind.begin(), kind.end(), kind.begin(), ::tolower);
340
341         if (kind == "feature") {
342                 return ContentKind::FEATURE;
343         } else if (kind == "short") {
344                 return ContentKind::SHORT;
345         } else if (kind == "trailer") {
346                 return ContentKind::TRAILER;
347         } else if (kind == "test") {
348                 return ContentKind::TEST;
349         } else if (kind == "transitional") {
350                 return ContentKind::TRANSITIONAL;
351         } else if (kind == "rating") {
352                 return ContentKind::RATING;
353         } else if (kind == "teaser") {
354                 return ContentKind::TEASER;
355         } else if (kind == "policy") {
356                 return ContentKind::POLICY;
357         } else if (kind == "psa") {
358                 return ContentKind::PUBLIC_SERVICE_ANNOUNCEMENT;
359         } else if (kind == "advertisement") {
360                 return ContentKind::ADVERTISEMENT;
361         } else if (kind == "episode") {
362                 return ContentKind::EPISODE;
363         } else if (kind == "promo") {
364                 return ContentKind::PROMO;
365         }
366
367         throw BadContentKindError (kind);
368 }
369
370
371 string
372 dcp::marker_to_string (dcp::Marker m)
373 {
374         switch (m) {
375         case Marker::FFOC:
376                 return "FFOC";
377         case Marker::LFOC:
378                 return "LFOC";
379         case Marker::FFTC:
380                 return "FFTC";
381         case Marker::LFTC:
382                 return "LFTC";
383         case Marker::FFOI:
384                 return "FFOI";
385         case Marker::LFOI:
386                 return "LFOI";
387         case Marker::FFEC:
388                 return "FFEC";
389         case Marker::LFEC:
390                 return "LFEC";
391         case Marker::FFMC:
392                 return "FFMC";
393         case Marker::LFMC:
394                 return "LFMC";
395         }
396
397         DCP_ASSERT (false);
398 }
399
400 dcp::Marker
401 dcp::marker_from_string (string s)
402 {
403         if (s == "FFOC") {
404                 return Marker::FFOC;
405         } else if (s == "LFOC") {
406                 return Marker::LFOC;
407         } else if (s == "FFTC") {
408                 return Marker::FFTC;
409         } else if (s == "LFTC") {
410                 return Marker::LFTC;
411         } else if (s == "FFOI") {
412                 return Marker::FFOI;
413         } else if (s == "LFOI") {
414                 return Marker::LFOI;
415         } else if (s == "FFEC") {
416                 return Marker::FFEC;
417         } else if (s == "LFEC") {
418                 return Marker::LFEC;
419         } else if (s == "FFMC") {
420                 return Marker::FFMC;
421         } else if (s == "LFMC") {
422                 return Marker::LFMC;
423         }
424
425         DCP_ASSERT (false);
426 }
427
428 Rating::Rating (cxml::ConstNodePtr node)
429 {
430         agency = node->string_child("Agency");
431         label = node->string_child("Label");
432         node->done ();
433 }
434
435 void
436 Rating::as_xml (xmlpp::Element* parent) const
437 {
438         parent->add_child("Agency")->add_child_text(agency);
439         parent->add_child("Label")->add_child_text(label);
440 }
441
442 bool
443 dcp::operator== (Rating const & a, Rating const & b)
444 {
445         return a.agency == b.agency && a.label == b.label;
446 }
447
448 ContentVersion::ContentVersion ()
449         : id ("urn:uuid:" + make_uuid())
450 {
451
452 }
453
454
455 ContentVersion::ContentVersion (cxml::ConstNodePtr node)
456 {
457         id = node->string_child("Id");
458         label_text = node->string_child("LabelText");
459 }
460
461
462 ContentVersion::ContentVersion (string label_text_)
463         : id ("urn:uuid:" + make_uuid())
464         , label_text (label_text_)
465 {
466
467 }
468
469
470 void
471 ContentVersion::as_xml (xmlpp::Element* parent) const
472 {
473         xmlpp::Node* cv = parent->add_child("ContentVersion");
474         cv->add_child("Id")->add_child_text(id);
475         cv->add_child("LabelText")->add_child_text(label_text);
476 }
477
478
479 Luminance::Luminance (cxml::ConstNodePtr node)
480 {
481         _unit = string_to_unit (node->string_attribute("units"));
482         _value = raw_convert<float> (node->content());
483 }
484
485
486 Luminance::Luminance (float value, Unit unit)
487         : _unit (unit)
488 {
489         set_value (value);
490 }
491
492
493 void
494 Luminance::set_value (float v)
495 {
496         if (v < 0) {
497                 throw dcp::MiscError (String::compose("Invalid luminance value %1", v));
498         }
499
500         _value = v;
501 }
502
503
504 void
505 Luminance::as_xml (xmlpp::Element* parent, string ns) const
506 {
507         xmlpp::Element* lum = parent->add_child("Luminance", ns);
508         lum->set_attribute("units", unit_to_string(_unit));
509         lum->add_child_text(raw_convert<string>(_value, 3));
510 }
511
512
513 string
514 Luminance::unit_to_string (Unit u)
515 {
516         switch (u) {
517         case Unit::CANDELA_PER_SQUARE_METRE:
518                 return "candela-per-square-metre";
519         case Unit::FOOT_LAMBERT:
520                 return "foot-lambert";
521         default:
522                 DCP_ASSERT (false);
523         }
524
525         return "";
526 }
527
528
529 Luminance::Unit
530 Luminance::string_to_unit (string u)
531 {
532         if (u == "candela-per-square-metre") {
533                 return Unit::CANDELA_PER_SQUARE_METRE;
534         } else if (u == "foot-lambert") {
535                 return Unit::FOOT_LAMBERT;
536         }
537
538         throw XMLError (String::compose("Invalid luminance unit %1", u));
539 }
540
541
542 bool
543 dcp::operator== (Luminance const& a, Luminance const& b)
544 {
545         return fabs(a.value() - b.value()) < 0.001 && a.unit() == b.unit();
546 }
547
548
549 MainSoundConfiguration::MainSoundConfiguration (string s)
550 {
551         vector<string> parts;
552         boost::split (parts, s, boost::is_any_of("/"));
553         if (parts.size() != 2) {
554                 throw MainSoundConfigurationError (s);
555         }
556
557         if (parts[0] == "51") {
558                 _field = MCASoundField::FIVE_POINT_ONE;
559         } else if (parts[0] == "71") {
560                 _field = MCASoundField::SEVEN_POINT_ONE;
561         } else {
562                 throw MainSoundConfigurationError (s);
563         }
564
565         vector<string> channels;
566         boost::split (channels, parts[1], boost::is_any_of(","));
567
568         if (channels.size() > 16) {
569                 throw MainSoundConfigurationError (s);
570         }
571
572         for (auto i: channels) {
573                 if (i == "-") {
574                         _channels.push_back(optional<Channel>());
575                 } else {
576                         _channels.push_back(mca_id_to_channel(i));
577                 }
578         }
579 }
580
581
582 MainSoundConfiguration::MainSoundConfiguration (MCASoundField field, int channels)
583         : _field (field)
584 {
585         _channels.resize (channels);
586 }
587
588
589 string
590 MainSoundConfiguration::to_string () const
591 {
592         string c;
593         if (_field == MCASoundField::FIVE_POINT_ONE) {
594                 c = "51/";
595         } else {
596                 c = "71/";
597         }
598
599         for (auto i: _channels) {
600                 if (!i) {
601                         c += "-,";
602                 } else {
603                         c += channel_to_mca_id(*i, _field) + ",";
604                 }
605         }
606
607         if (c.length() > 0) {
608                 c = c.substr(0, c.length() - 1);
609         }
610
611         return c;
612 }
613
614
615 optional<Channel>
616 MainSoundConfiguration::mapping (int index) const
617 {
618         DCP_ASSERT (static_cast<size_t>(index) < _channels.size());
619         return _channels[index];
620 }
621
622
623 void
624 MainSoundConfiguration::set_mapping (int index, Channel c)
625 {
626         DCP_ASSERT (static_cast<size_t>(index) < _channels.size());
627         _channels[index] = c;
628 }
629
630
631 string
632 dcp::status_to_string (Status s)
633 {
634         switch (s) {
635         case Status::FINAL:
636                 return "final";
637         case Status::TEMP:
638                 return "temp";
639         case Status::PRE:
640                 return "pre";
641         default:
642                 DCP_ASSERT (false);
643         }
644
645 }
646
647
648 Status
649 dcp::string_to_status (string s)
650 {
651         if (s == "final") {
652                 return Status::FINAL;
653         } else if (s == "temp") {
654                 return Status::TEMP;
655         } else if (s == "pre") {
656                 return Status::PRE;
657         }
658
659         DCP_ASSERT (false);
660 }
661
662
663 Channel
664 dcp::mca_id_to_channel (string id)
665 {
666         if (id == "L") {
667                 return Channel::LEFT;
668         } else if (id == "R") {
669                 return Channel::RIGHT;
670         } else if (id == "C") {
671                 return Channel::CENTRE;
672         } else if (id == "LFE") {
673                 return Channel::LFE;
674         } else if (id == "Ls" || id == "Lss") {
675                 return Channel::LS;
676         } else if (id == "Rs" || id == "Rss") {
677                 return Channel::RS;
678         } else if (id == "HI") {
679                 return Channel::HI;
680         } else if (id == "VIN") {
681                 return Channel::VI;
682         } else if (id == "Lrs") {
683                 return Channel::BSL;
684         } else if (id == "Rrs") {
685                 return Channel::BSR;
686         } else if (id == "DBOX") {
687                 return Channel::MOTION_DATA;
688         } else if (id == "FSKSync") {
689                 return Channel::SYNC_SIGNAL;
690         } else if (id == "SLVS") {
691                 return Channel::SIGN_LANGUAGE;
692         }
693
694         throw UnknownChannelIdError (id);
695 }
696
697
698 string
699 dcp::channel_to_mca_id (Channel c, MCASoundField field)
700 {
701         switch (c) {
702         case Channel::LEFT:
703                 return "L";
704         case Channel::RIGHT:
705                 return "R";
706         case Channel::CENTRE:
707                 return "C";
708         case Channel::LFE:
709                 return "LFE";
710         case Channel::LS:
711                 return field == MCASoundField::FIVE_POINT_ONE ? "Ls" : "Lss";
712         case Channel::RS:
713                 return field == MCASoundField::FIVE_POINT_ONE ? "Rs" : "Rss";
714         case Channel::HI:
715                 return "HI";
716         case Channel::VI:
717                 return "VIN";
718         case Channel::BSL:
719                 return "Lrs";
720         case Channel::BSR:
721                 return "Rrs";
722         case Channel::MOTION_DATA:
723                 return "DBOX";
724         case Channel::SYNC_SIGNAL:
725                 return "FSKSync";
726         case Channel::SIGN_LANGUAGE:
727                 return "SLVS";
728         default:
729                 break;
730         }
731
732         DCP_ASSERT (false);
733 }
734
735
736 string
737 dcp::channel_to_mca_name (Channel c, MCASoundField field)
738 {
739         switch (c) {
740         case Channel::LEFT:
741                 return "Left";
742         case Channel::RIGHT:
743                 return "Right";
744         case Channel::CENTRE:
745                 return "Center";
746         case Channel::LFE:
747                 return "LFE";
748         case Channel::LS:
749                 return field == MCASoundField::FIVE_POINT_ONE ? "Left Surround" : "Left Side Surround";
750         case Channel::RS:
751                 return field == MCASoundField::FIVE_POINT_ONE ? "Right Surround" : "Right Side Surround";
752         case Channel::HI:
753                 return "Hearing Impaired";
754         case Channel::VI:
755                 return "Visually Impaired-Narrative";
756         case Channel::BSL:
757                 return "Left Rear Surround";
758         case Channel::BSR:
759                 return "Right Rear Surround";
760         case Channel::MOTION_DATA:
761                 return "D-BOX Motion Code Primary Stream";
762         case Channel::SYNC_SIGNAL:
763                 return "FSK Sync";
764         case Channel::SIGN_LANGUAGE:
765                 return "Sign Language Video Stream";
766         default:
767                 break;
768         }
769
770         DCP_ASSERT (false);
771 }
772
773
774 ASDCP::UL
775 dcp::channel_to_mca_universal_label (Channel c, MCASoundField field, ASDCP::Dictionary const* dict)
776 {
777         static byte_t sync_signal[] = {
778                 0x06, 0x0e, 0x2b, 0x34, 0x04, 0x01, 0x01, 0x0d, 0x03, 0x02, 0x01, 0x10, 0x00, 0x00, 0x00, 0x00
779         };
780
781         static byte_t sign_language[] = {
782                 0x06, 0x0e, 0x2b, 0x34, 0x04, 0x01, 0x01, 0x0d, 0x0d, 0x0f, 0x03, 0x02, 0x01, 0x01, 0x00, 0x00
783         };
784
785         switch (c) {
786         case Channel::LEFT:
787                 return dict->ul(ASDCP::MDD_DCAudioChannel_L);
788         case Channel::RIGHT:
789                 return dict->ul(ASDCP::MDD_DCAudioChannel_R);
790         case Channel::CENTRE:
791                 return dict->ul(ASDCP::MDD_DCAudioChannel_C);
792         case Channel::LFE:
793                 return dict->ul(ASDCP::MDD_DCAudioChannel_LFE);
794         case Channel::LS:
795                 return dict->ul(field == MCASoundField::FIVE_POINT_ONE ? ASDCP::MDD_DCAudioChannel_Ls : ASDCP::MDD_DCAudioChannel_Lss);
796         case Channel::RS:
797                 return dict->ul(field == MCASoundField::FIVE_POINT_ONE ? ASDCP::MDD_DCAudioChannel_Rs : ASDCP::MDD_DCAudioChannel_Rss);
798         case Channel::HI:
799                 return dict->ul(ASDCP::MDD_DCAudioChannel_HI);
800         case Channel::VI:
801                 return dict->ul(ASDCP::MDD_DCAudioChannel_VIN);
802         case Channel::BSL:
803                 return dict->ul(ASDCP::MDD_DCAudioChannel_Lrs);
804         case Channel::BSR:
805                 return dict->ul(ASDCP::MDD_DCAudioChannel_Rrs);
806         case Channel::MOTION_DATA:
807                 return dict->ul(ASDCP::MDD_DBOXMotionCodePrimaryStream);
808         case Channel::SYNC_SIGNAL:
809                 return ASDCP::UL(sync_signal);
810         case Channel::SIGN_LANGUAGE:
811                 return ASDCP::UL(sign_language);
812         default:
813                 break;
814         }
815
816         DCP_ASSERT (false);
817 }
818
819
820 vector<dcp::Channel>
821 dcp::used_audio_channels ()
822 {
823         vector<Channel> c;
824         c.push_back (Channel::LEFT);
825         c.push_back (Channel::RIGHT);
826         c.push_back (Channel::CENTRE);
827         c.push_back (Channel::LFE);
828         c.push_back (Channel::LS);
829         c.push_back (Channel::RS);
830         c.push_back (Channel::HI);
831         c.push_back (Channel::VI);
832         c.push_back (Channel::BSL);
833         c.push_back (Channel::BSR);
834         c.push_back (Channel::MOTION_DATA);
835         c.push_back (Channel::SYNC_SIGNAL);
836         c.push_back (Channel::SIGN_LANGUAGE);
837         return c;
838 }
839