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