附加示例sndpeek 程序和 cmdWhistle.pl 提供了一种可以以独特方式使用的附加用户输入机制。比如,为屏幕保护程序设置解锁代码并在接近电脑的时候吹一下口哨 —— 再无需烦人的密码键入操作。又如,吹一下口哨即可让计算机为您检查电子邮件,或为您检查移动电话的独特铃声,并在铃声响起时向您发送一封电子邮件。
cmdWhistle.pl 代码历史和策略快速傅立叶变换、滑动窗口和某些 Linux 音频编程可以为您提供符合您所选语言的音调识别能力。Ge Wang、Perry R. Cook 和 Ananya Misra 编写的 sndpeek 应用程序提供了一种可移植的、快速的 UNIX® 式的方法来采集 cmdWhistle.pl 检测音调事件所需的信息。简单的命令 sndpeek --print 将显示对当前声音源的实时文本分析,并提供优秀的 3D 视觉效果。sndpeek 打印出的文本分析中的第四个条目是使用 sndtools 中的 “Marsyas” 组件进行 sndpeeks 处理的 Rolloff 函数的输出。下面是来自 Rolloff.cpp 源代码的描述:
清单 4. 来自 Rolloff.cpp 源代码的描述1
2
3
4
| Compute Rolloff (a percentile point) of the input fvec.
For example if perc_ is 0.90 then Rolloff would be the
index where the sum of values before that index are
equal to 90% of the total energy.
|
使用此值作为我们的基准 “音调”,cmdWhistle.pl 脚本将检测音调之间的各种时间间隔。
参数配置让我们从位于 cmdWhistle.pl 顶部的计时和传感器临界参数开始:
清单 5. cmdWhistle.pl 计时和传感器临界参数1
2
3
4
5
6
7
8
9
10
11
12
| $|=1; #for non buffered standard output, useful for other programs to read
require 'sys/syscall.ph'; # for subsecond timing
my $option = $ARGV[0] || ""; # simple option handling
my $MAX_TIMEOUT_LENGTH = 4; # maximum length in seconds of tone pattern
my $LISTEN_TIMEOUT = 2; # timeout value in seconds between tone
my $MAX_TIME_DEV = 100000; # maximum acceptable deviation between recorded
# pattern values and current time values
my $MAX_TONE_DEV = 2; # maximum acceptable deviation between recorded
# pattern values and current tone values
my $MAX_AVG_TONE = 5; # maximum number of samples to be averaged
|
以上变量及其注释相对来说都很简单。稍后将更详细地介绍其用法和配置选项。下面是其余的全局变量及其描述。
清单 6. 其余的全局变量和描述1
2
3
4
5
6
7
8
9
10
11
| my @queTones = (); # running queue of recent tones detected
my $prevTone = 0; # the last solid tone detected, used for disambiguation
my $prevInterval = 0; # previous interval of time
my @baseTones = (); # the currently entered tone sequence
my @baseTimes = (); # the currently entered temporal values
my %toneHash = (); # tones, times and commands read from ~/.toneFile
my $toneCount = 0; # the current count of tones entered
my $startTime = ""; # start of a temporal block
my $currTime = ""; # current time in the time out loop
my $toneAge = 0; # for tone count synchronization
my $timeOut = 0; # to reset the timer window
|
子例程我们从 getEpochSeconds 和 getEpochMicroSeconds 子例程开始,它们用于提供关于时间音调模式状态的详细且精确的信息。
清单 7. getEpochSeconds 和 getEpochMicroSeconds 子例程1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
| sub getEpochMicroSeconds {
my $TIMEVAL_T = "LL"; # LL for microseconds
my $timeVal = pack($TIMEVAL_T, ());
syscall(&SYS_gettimeofday, $timeVal, 0) != -1 or die "micro seconds: $!";
my @vals = unpack( $TIMEVAL_T, $timeVal );
$timeVal = $vals[0] . $vals[1];
$timeVal = substr( $timeVal, 6);
my $padLen = 10 - length($timeVal);
$timeVal = $timeVal . "0" x $padLen;
return($timeVal);
}#getEpochMicroSeconds
sub getEpochSeconds {
my $TIMEVAL_T = "LL"; # LL for microseconds
my $start = pack($TIMEVAL_T, ());
syscall(&SYS_gettimeofday, $start, 0) != -1 or die "seconds: $!";
return( (unpack($TIMEVAL_T, $start))[0] );
}#getEpochSeconds
|
接下来是 readTones 子例程,它首先将采集 Rolloff 数据值作为 sndpeek 的输出。您可以从随后的注释部分中看到,代码首先将开发五种样例音调并一起比较以形成计算偏差的依据。如果音调数组队列取决于容量,就可以计算每个音调的偏差。如果对队列中所有单音调的比较结果超过音调偏差的最大值,则指定当前处理不生成可辨别的音调。
如果队列中所有音调的偏差小于可接受的阈值,则执行模糊检测的时间层。如果以缓慢升高的音调吹口哨,则您个人的音调与生成可识别的音调事件的可接受阈值之间会有极小的偏差。但这种不断的检测可能会导致出现问题。当偏差值超过 MAX_TONE_DEV 变量时,upDev 和 downDev 变量以及比较逻可用于采集连续音调变化。如果最新音调队列检查和连续音调检查都通过,就记录音调事件及其时间以供稍后打印或比较。
仔细修改这些变量将帮助调整程序来识别特定的音调样式和时间偏差。频率分析的整体刷新率由 sndpeek 程序的输出决定。所有其他参数都可以被配置为检测间隔更长的音调事件 —— 在一个时间阈值内发生多个并行音调事件,或者音调事件之间的时间间隔不同。
清单 8. 检测间隔更大的音调事件1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
| sub readTones
{
# read the Rolloff output only,
my(undef, undef, undef, $currentTone ) = split " ", $_[0];
if( @queTones == $MAX_AVG_TONE )
{
my $localDiff = 0;
# check for a solid tone by comparing against the last five tones
# perform simple time related tonal smoothing, so if the tone
# wavers just a bit it's still counted as one tone
for my $chkHistTone ( @queTones )
{
if( abs($currentTone - $chkHistTone) > $MAX_TONE_DEV )
{
$localDiff =1;
$prevTone = 0;
}#if deviation less than threshold
}#for each tone
if( $localDiff == 0 )
{
# make sure the current tone is different than the previous one, this is to
# ensure that long duration tones are not acquired as multiple tone events
# this step up or down will allow you to whistle continuously and pick up the
# steps as discrete tone events
my $upDev = $currentTone + $MAX_TONE_DEV;
my $downDev = $currentTone - $MAX_TONE_DEV;
if( $prevTone > $upDev || $prevTone < $downDev )
{
my $currVal = getEpochMicroSeconds();
my $diffInterval = abs($prevInterval - $currVal);
if( $option ){
print "Tone: $currentTone ## last: [$currVal] curr: [$prevInterval] ";
print "difference is: $diffInterval\n";
}
if( $toneCount == 0 ){ $diffInterval = 0 }
push @baseTones, $currentTone;
push @baseTimes, $diffInterval;
$toneCount++;
$prevInterval = $currVal;
}#if deviation in tone
# now set the previous tone to the current tone so a continuous tone
# is not acquired as multiple tone events
$prevTone = $currentTone;
}#if a solid tone has been found
# if enough tones to create an useful queue have been added, pop one off
shift @queTones;
}#if enough tones to create a useful queue
# always push more tones on the avg
push @queTones, $currentTone;
}#readTones
|
|