File Coverage

blib/lib/Protocol/OSC.pm
Criterion Covered Total %
statement 57 70 81.4
branch 13 22 59.0
condition 5 15 33.3
subroutine 19 25 76.0
pod 13 13 100.0
total 107 145 73.7


line stmt bran cond sub pod time code
1             # ABSTRACT: Open Sound Control v1.1 protocol implementation
2 2     2   134461 use strict;
  2         15  
  2         58  
3 2     2   11 use warnings;
  2         5  
  2         87  
4              
5             package Protocol::OSC;
6             $Protocol::OSC::VERSION = '0.09';
7 2     2   20 use Scalar::Util 'looks_like_number';
  2         4  
  2         122  
8 2     2   13 use constant { NTP_EPOCH_DIFF => 2208988800, MAX_INT => 2**32 };
  2         4  
  2         3036  
9             my %converter = qw(i N f N s Z*x!4 b N/a*x!4 h h t N2);
10             my %filter = (f => [qw'f L']);
11             if (pack('f>', 0.5) eq pack N => unpack L => pack f => 0.5) { # f> is ieee754 compatible
12             delete$filter{f}; $converter{f} = 'f>' }
13             my $has_filters = keys%filter;
14              
15             sub new { bless {
16 1     1   4 scheduler => sub { $_[0]->(splice @_, 1) },
17 2     2 1 222 actions => {},
18             splice(@_, 1),
19             }, shift }
20              
21             sub parse {
22 18     18 1 6162 my ($self, $data) = @_;
23 18 100       69 if ((my $f = substr $data, 0, 1) eq '#') { # bundle
    50          
24 2         23 my (undef, $time, $fraction, @msgs) = unpack 'Z8N2(N/a*)*', $data;
25 2         8 Protocol::OSC::Bundle->new($self->tag2time($time, $fraction), map $self->parse($_), @msgs);
26             } elsif ($f eq '/') { # message
27 16         76 my ($path, $type, $args) = unpack '(Z*x!4)2a*', $data;
28 16         38 substr $type, 0, 1, '';
29 16   33     144 my @args = unpack join('', my @types = map $converter{$_} || (), split '', $type), $args;
30 16 50       45 if ($has_filters) { for (grep exists$filter{$_->[1]}, map [$_, $types[$_]], 0..$#types) {
  0         0  
31 0         0 my $f = $filter{$_->[1]};
32 0         0 $args[$_->[0]] = unpack $f->[0], pack $f->[1], $args[$_->[0]] } }
33 16         45 Protocol::OSC::Message->new( $path, $type, @args );
34 0         0 } else { warn 'broken osc packet' } }
35              
36             sub bundle {
37 2     2 1 19 my ($self, $time, @msgs) = @_;
38             pack 'Z8N2(N/a*)*', '#bundle', $self->time2tag($time), map {
39 2 50 33     6 $self->${\( defined $_->[0] && !looks_like_number $_->[0] ? 'message' : 'bundle' )}(@{$_})
  4         8  
  4         31  
  4         8  
40             } @msgs }
41              
42             *msg = \&message;
43             sub message {
44 5     5 1 15 my ($self, $path, $type, @args) = @_;
45             pack '(Z*x!4)2a*', $path, ','.$type,
46             join '', map pack($converter{$_},
47             $has_filters && exists$filter{$_}
48             ? unpack $filter{$_}[1], pack $filter{$_}[0], shift@args
49             : shift@args),
50 5 50 33     105 grep exists$converter{$_}, split //, $type }
51              
52             sub process {
53 2     2 1 6 my ($self, $packet, $scheduler_cb, $at_time, @bundle) = @_;
54 2 50       8 if ((my $r = ref$packet) eq 'Protocol::OSC::Bundle') {
    100          
55 0         0 map $self->process($_, $scheduler_cb, $packet->[0], $packet, @bundle), splice @$packet, 1;
56             } elsif ($r eq 'Protocol::OSC::Message') {
57             map {
58 1   33     6 ( $scheduler_cb || $self->{scheduler} )->($_->[1], $at_time, $_->[0], $packet, @bundle)
  1         8  
59             } $self->match($packet->[0]);
60 1         3 } else { $self->process($self->parse($packet), $scheduler_cb, $at_time, @bundle) } }
61              
62 0     0 1 0 sub actions { $_[0]{actions} }
63              
64 1     1 1 7 sub set_cb { $_[0]{actions}{$_[1]} = $_[2] }
65              
66 0     0 1 0 sub del_cb { delete $_[0]{actions}{$_[1]} }
67              
68             sub match {
69 1     1 1 3 my ($self, $pattern) = @_;
70 1         5 $pattern =~ s!(\*|//)!.+!g;
71 1         4 $pattern =~ y/?{},!/.()^|/;
72 1         2 map [$_, $self->{actions}->{$_}], grep /^$pattern$/, keys%{$self->{actions}} }
  1         20  
73              
74             sub tag2time {
75 2     2 1 4 my ($self, $secs, $frac) = @_;
76 2 50 33     7 return undef if !$secs && $frac == 1;
77 2         12 $secs - NTP_EPOCH_DIFF + $frac / MAX_INT }
78              
79             sub time2tag {
80 2     2 1 5 my ($self, $t) = @_;
81 2 50       5 return (0, 1) unless $t;
82 2         5 my $secs = int($t);
83 2         10 ( $secs + NTP_EPOCH_DIFF, int MAX_INT * ($t - $secs) ) }
84              
85 1     1 1 5 sub to_stream { pack 'N/a*' => $_[1] }
86              
87             sub from_stream {
88 1     1 1 2 my ($self, $buf) = @_;
89 1 50       4 return $buf if length $buf < 4;
90 1         4 my $n = unpack 'N', substr $buf, 0, 4;
91 1 50       3 return $buf if length $buf < $n + 4;
92 1         7 (unpack('N/a*', substr $buf, 0, 4+$n, ''), $buf) }
93              
94             package Protocol::OSC::Message;
95             $Protocol::OSC::Message::VERSION = '0.09';
96 16     16   101 sub new { bless [splice(@_,1)], shift }
97 0     0   0 sub path { $_[0][0] }
98 1     1   315 sub type { $_[0][1] }
99 0     0   0 sub args { my $self = shift; @$self[2..$#$self] }
  0         0  
100              
101             package Protocol::OSC::Bundle;
102             $Protocol::OSC::Bundle::VERSION = '0.09';
103 2     2   37 sub new { bless [splice(@_,1)], shift }
104 0     0     sub time { $_[0][0] }
105 0     0     sub packets { my $self = shift; @$self[1..$#$self] }
  0            
106              
107             1;