line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package Audio::Analyzer::ToneDetect; |
2
|
|
|
|
|
|
|
|
3
|
1
|
|
|
1
|
|
20237
|
use strict; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
38
|
|
4
|
1
|
|
|
1
|
|
5
|
use warnings; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
25
|
|
5
|
1
|
|
|
1
|
|
19
|
use 5.010; |
|
1
|
|
|
|
|
7
|
|
|
1
|
|
|
|
|
50
|
|
6
|
|
|
|
|
|
|
|
7
|
|
|
|
|
|
|
our $VERSION = '0.04'; |
8
|
|
|
|
|
|
|
|
9
|
1
|
|
|
1
|
|
788
|
use Audio::Analyzer; |
|
1
|
|
|
|
|
9766
|
|
|
1
|
|
|
|
|
26
|
|
10
|
1
|
|
|
1
|
|
8
|
use Carp; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
58
|
|
11
|
1
|
|
|
1
|
|
1068
|
use Sort::Key::Top 'rnkeytop'; |
|
1
|
|
|
|
|
1768
|
|
|
1
|
|
|
|
|
1318
|
|
12
|
|
|
|
|
|
|
|
13
|
|
|
|
|
|
|
sub new { |
14
|
3
|
|
|
3
|
1
|
3499
|
my ( $class, %args ) = @_; |
15
|
|
|
|
|
|
|
|
16
|
3
|
|
|
|
|
12
|
my $self = bless {}, $class; |
17
|
|
|
|
|
|
|
|
18
|
3
|
|
50
|
|
|
23
|
$self->{source} = delete $args{source} || \*STDIN; |
19
|
3
|
|
50
|
|
|
19
|
$self->{sample_rate} = delete $args{sample_rate} || 16000; |
20
|
3
|
|
50
|
|
|
17
|
$self->{chunk_size} = delete $args{chunk_size} || 1024; |
21
|
3
|
|
50
|
|
|
14
|
$self->{chunk_max} = delete $args{chunk_max} || 70; |
22
|
3
|
|
50
|
|
|
17
|
$self->{min_tone_length} = delete $args{min_tone_length} || 0.5; |
23
|
3
|
|
|
|
|
7
|
$self->{valid_tones} = delete $args{valid_tones}; |
24
|
3
|
|
|
|
|
9
|
$self->{valid_error_cb} = delete $args{valid_error_cb}; |
25
|
3
|
|
50
|
|
|
20
|
$self->{rejected_freqs} = delete $args{rejected_freqs} || []; |
26
|
|
|
|
|
|
|
|
27
|
3
|
100
|
66
|
|
|
18
|
if ( $self->{valid_tones} && $self->{valid_tones} eq 'builtin' ) { |
28
|
2
|
|
|
|
|
8
|
$self->{valid_tones} = _get_builtin_tones(); |
29
|
|
|
|
|
|
|
} |
30
|
|
|
|
|
|
|
|
31
|
|
|
|
|
|
|
croak( |
32
|
3
|
50
|
|
|
|
13
|
"Invalid chunk_size ($self->{chunk_size}). chunk_size must be power of 2" |
33
|
|
|
|
|
|
|
) unless _is_pow_of_two( $self->{chunk_size} ); |
34
|
|
|
|
|
|
|
|
35
|
3
|
|
|
|
|
16
|
$self->{chunks_required} |
36
|
|
|
|
|
|
|
= int( |
37
|
|
|
|
|
|
|
$self->{min_tone_length} * $self->{sample_rate} / $self->{chunk_size} ); |
38
|
|
|
|
|
|
|
|
39
|
3
|
|
|
|
|
21
|
$self->{analyzer} = Audio::Analyzer->new( |
40
|
|
|
|
|
|
|
file => $self->{source}, |
41
|
|
|
|
|
|
|
sample_rate => $self->{sample_rate}, |
42
|
|
|
|
|
|
|
dft_size => $self->{chunk_size}, |
43
|
|
|
|
|
|
|
channels => 1, |
44
|
|
|
|
|
|
|
%args |
45
|
|
|
|
|
|
|
); |
46
|
|
|
|
|
|
|
|
47
|
3
|
|
|
|
|
12935
|
$self->{freqs} = $self->{analyzer}->freqs; |
48
|
|
|
|
|
|
|
|
49
|
3
|
|
|
|
|
35
|
return $self; |
50
|
|
|
|
|
|
|
} |
51
|
|
|
|
|
|
|
|
52
|
|
|
|
|
|
|
sub valid_tones { |
53
|
3
|
|
|
3
|
1
|
915
|
my ( $self, $new_tone_map ) = @_; |
54
|
3
|
100
|
|
|
|
16
|
$self->{valid_tones} = [ sort @$new_tone_map ] if $new_tone_map; |
55
|
3
|
|
|
|
|
7
|
return $self->{valid_tones}; |
56
|
|
|
|
|
|
|
} |
57
|
|
|
|
|
|
|
|
58
|
|
|
|
|
|
|
sub get_next_tone { |
59
|
10
|
|
|
10
|
1
|
1469
|
my $self = shift; |
60
|
10
|
|
|
|
|
19
|
my $min_tone_length = shift; |
61
|
|
|
|
|
|
|
|
62
|
10
|
|
|
|
|
13
|
state $last_detected = 0; |
63
|
10
|
|
|
|
|
15
|
state @buff; |
64
|
|
|
|
|
|
|
|
65
|
10
|
|
|
|
|
12
|
my $chunks_required; |
66
|
10
|
50
|
|
|
|
29
|
if ($min_tone_length) { |
67
|
0
|
|
|
|
|
0
|
$chunks_required = int( |
68
|
|
|
|
|
|
|
$min_tone_length * $self->{sample_rate} / $self->{chunk_size} ); |
69
|
|
|
|
|
|
|
} |
70
|
10
|
|
33
|
|
|
57
|
$chunks_required ||= $self->{chunks_required}; |
71
|
|
|
|
|
|
|
|
72
|
10
|
|
|
|
|
17
|
my $chunk_count = 0; |
73
|
10
|
|
|
|
|
31
|
while ( $chunk_count < $self->{chunk_max} ) { |
74
|
282
|
|
|
|
|
375
|
$chunk_count++; |
75
|
282
|
|
|
|
|
1264
|
my $chunk = $self->{analyzer}->next; |
76
|
282
|
|
|
|
|
1071217
|
my $fft = $chunk->fft; |
77
|
282
|
|
|
144384
|
|
655792
|
my $top = rnkeytop { $fft->[0][$_] } 1 => 0 .. $#{ $fft->[0] }; |
|
144384
|
|
|
|
|
230139
|
|
|
282
|
|
|
|
|
6849
|
|
78
|
282
|
|
|
|
|
4643
|
my $detected_freq = $self->{freqs}[$top]; |
79
|
282
|
100
|
|
|
|
6784
|
next if $detected_freq == $last_detected; |
80
|
100
|
50
|
|
|
|
137
|
next if grep { $_ == $detected_freq } @{ $self->{rejected_freqs} }; |
|
0
|
|
|
|
|
0
|
|
|
100
|
|
|
|
|
328
|
|
81
|
|
|
|
|
|
|
|
82
|
100
|
|
|
|
|
193
|
push @buff, $detected_freq; |
83
|
100
|
100
|
|
|
|
285
|
shift @buff if @buff > $chunks_required; |
84
|
100
|
100
|
100
|
|
|
661
|
next unless @buff == $chunks_required && _all_match( \@buff ); |
85
|
10
|
|
|
|
|
20
|
$last_detected = $detected_freq; |
86
|
|
|
|
|
|
|
|
87
|
10
|
100
|
|
|
|
189
|
return $detected_freq unless $self->{valid_tones}; |
88
|
|
|
|
|
|
|
|
89
|
6
|
|
|
|
|
29
|
my ( $valid_tone, $delta ) = $self->find_closest_valid($detected_freq); |
90
|
6
|
100
|
|
|
|
51
|
next unless $valid_tone; |
91
|
5
|
100
|
|
|
|
323
|
return wantarray ? ( $valid_tone, $delta ) : $valid_tone; |
92
|
|
|
|
|
|
|
} |
93
|
1
|
|
|
|
|
9
|
return; |
94
|
|
|
|
|
|
|
} |
95
|
|
|
|
|
|
|
|
96
|
|
|
|
|
|
|
sub get_next_two_tones { |
97
|
3
|
|
|
3
|
1
|
551
|
my $self = shift; |
98
|
3
|
|
|
|
|
6
|
my ( $tone_a_length, $tone_b_length ) = @_; |
99
|
|
|
|
|
|
|
|
100
|
3
|
|
50
|
|
|
9
|
my $tone_a = $self->get_next_tone($tone_a_length) || return; |
101
|
3
|
|
50
|
|
|
15
|
my $tone_b = $self->get_next_tone($tone_b_length) || return; |
102
|
3
|
100
|
|
|
|
58
|
return wantarray ? ( $tone_a, $tone_b ) : "$tone_a $tone_b"; |
103
|
|
|
|
|
|
|
} |
104
|
|
|
|
|
|
|
|
105
|
|
|
|
|
|
|
sub find_closest_valid { |
106
|
6
|
|
|
6
|
1
|
12
|
my ( $self, $freq ) = @_; |
107
|
6
|
|
|
|
|
12
|
my $lower = 0; |
108
|
6
|
|
|
|
|
10
|
my $upper; |
109
|
|
|
|
|
|
|
|
110
|
6
|
|
|
|
|
11
|
for my $possibility ( @{ $self->{valid_tones} } ) { |
|
6
|
|
|
|
|
19
|
|
111
|
842
|
100
|
|
|
|
1290
|
last if $upper; |
112
|
836
|
100
|
|
|
|
1533
|
$lower = $possibility if $possibility <= $freq; |
113
|
836
|
100
|
|
|
|
1328
|
$upper = $possibility if $possibility > $freq; |
114
|
|
|
|
|
|
|
} |
115
|
6
|
|
33
|
|
|
21
|
$upper ||= $lower; |
116
|
6
|
100
|
|
|
|
25
|
my $valid_tone |
117
|
|
|
|
|
|
|
= ( $freq - $lower ) < ( $upper - $freq ) |
118
|
|
|
|
|
|
|
? $lower |
119
|
|
|
|
|
|
|
: $upper; |
120
|
|
|
|
|
|
|
|
121
|
6
|
100
|
|
|
|
24
|
if ( $self->{valid_error_cb} ) { |
122
|
3
|
|
|
|
|
21
|
my $cb_result = $self->{valid_error_cb} |
123
|
|
|
|
|
|
|
->( $valid_tone, $freq, $freq - $valid_tone ); |
124
|
3
|
100
|
|
|
|
21
|
if ( defined $cb_result ) { |
125
|
2
|
100
|
|
|
|
7
|
return if $cb_result == 0; |
126
|
1
|
|
|
|
|
2
|
$valid_tone = $cb_result; |
127
|
|
|
|
|
|
|
} |
128
|
|
|
|
|
|
|
} |
129
|
|
|
|
|
|
|
|
130
|
5
|
|
|
|
|
20
|
return ( $valid_tone, $freq - $valid_tone ); |
131
|
|
|
|
|
|
|
} |
132
|
|
|
|
|
|
|
|
133
|
94
|
|
100
|
94
|
|
167
|
sub _all_match { my $l = shift; $_ == $l->[0] || return 0 for @$l; return 1 } |
|
94
|
|
|
|
|
3629
|
|
|
10
|
|
|
|
|
48
|
|
134
|
|
|
|
|
|
|
|
135
|
|
|
|
|
|
|
sub _is_pow_of_two { |
136
|
|
|
|
|
|
|
|
137
|
|
|
|
|
|
|
# if pow of 2 exactly 1 bit is set all others unset and n - 1 will have |
138
|
|
|
|
|
|
|
# that bit unset and all lower bits set thus binary AND of n & n -1 will |
139
|
|
|
|
|
|
|
# result in 0 |
140
|
3
|
|
33
|
3
|
|
22
|
return $_[0] != 0 && ( $_[0] & ( $_[0] - 1 ) ) == 0; |
141
|
|
|
|
|
|
|
} |
142
|
|
|
|
|
|
|
|
143
|
|
|
|
|
|
|
sub _get_builtin_tones { |
144
|
|
|
|
|
|
|
|
145
|
|
|
|
|
|
|
# via http://sourceforge.net/projects/tonedetect/ not sure complete/accurate |
146
|
|
|
|
|
|
|
# want better list |
147
|
|
|
|
|
|
|
|
148
|
2
|
|
|
2
|
|
48
|
return [ ( qw ( |
149
|
|
|
|
|
|
|
282.2 288.5 294.7 296.5 304.7 307.8 313.0 321.4 321.7 |
150
|
|
|
|
|
|
|
330.5 335.6 339.6 346.7 349.0 350.5 358.6 358.9 366.0 |
151
|
|
|
|
|
|
|
368.5 371.5 378.6 382.3 384.6 389.0 398.1 399.2 399.8 |
152
|
|
|
|
|
|
|
410.8 412.1 416.9 422.1 426.6 433.7 435.3 441.6 445.7 |
153
|
|
|
|
|
|
|
454.6 457.1 457.9 470.5 473.2 474.8 483.5 489.8 495.8 |
154
|
|
|
|
|
|
|
496.8 507.0 510.5 517.5 517.8 524.6 524.8 532.5 539.0 |
155
|
|
|
|
|
|
|
540.7 543.3 547.5 553.9 562.3 562.5 564.7 569.1 577.5 |
156
|
|
|
|
|
|
|
582.1 584.8 589.7 592.5 600.9 602.6 604.2 607.5 615.8 |
157
|
|
|
|
|
|
|
617.4 622.5 623.7 631.5 634.5 637.5 640.6 643.0 645.7 |
158
|
|
|
|
|
|
|
651.9 652.6 662.3 667.5 668.3 669.9 672.0 682.5 688.3 |
159
|
|
|
|
|
|
|
691.8 693.0 697.5 701.0 707.3 712.5 716.7 726.8 727.1 |
160
|
|
|
|
|
|
|
727.5 732.0 741.3 746.8 757.5 761.3 765.0 767.4 767.4 |
161
|
|
|
|
|
|
|
772.5 787.5 788.5 794.3 795.4 799.0 802.5 810.2 817.5 |
162
|
|
|
|
|
|
|
822.2 832.5 832.5 832.9 834.0 847.5 851.1 855.5 862.5 |
163
|
|
|
|
|
|
|
870.5 871.0 877.5 879.0 881.0 892.5 903.2 907.9 910.0 |
164
|
|
|
|
|
|
|
911.5 912.0 922.5 928.1 937.5 944.1 950.0 952.4 952.5 |
165
|
|
|
|
|
|
|
953.7 967.5 977.2 979.9 984.4 992.0 996.8 1006.9 1011.6 |
166
|
|
|
|
|
|
|
1034.7 1036.0 1041.2 1047.1 1063.2 1082.0 1084.0 1089.0 1092.4 |
167
|
|
|
|
|
|
|
1122.1 1122.5 1130.0 1140.2 1153.4 1161.4 1180.0 1185.2 1191.4 |
168
|
|
|
|
|
|
|
1217.8 1232.0 1246.0 1251.4 1285.8 1287.0 1304.0 1321.2 1344.0 |
169
|
|
|
|
|
|
|
1357.6 1362.1 1395.0 1403.0 1423.5 1433.4 1465.0 1488.4 1530.0 |
170
|
|
|
|
|
|
|
1556.7 1598.0 1628.3 1642.0 1669.0 1717.1 1743.0 1795.6 1820.0 |
171
|
|
|
|
|
|
|
1877.5 1901.0 1985.0 2051.6 2073.0 2143.8 2164.0 2260.0 2341.8 |
172
|
|
|
|
|
|
|
2361.0 2447.6 2465.0 2556.9 2575.0 2672.9 2688.0 2792.4 2807.0 |
173
|
|
|
|
|
|
|
2932.0 3062.0 3197.0 3339.0 3487.0 ) |
174
|
|
|
|
|
|
|
) ]; |
175
|
|
|
|
|
|
|
} |
176
|
|
|
|
|
|
|
|
177
|
|
|
|
|
|
|
1; |
178
|
|
|
|
|
|
|
__END__ |