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; |